《趣学Python——教孩子学编程》学习笔记第13章

96
麦典威
2018.04.04 00:21 字数 867

第13章 你的第一个游戏:弹球

13.1 打击反弹球

我们将要开发一个有反弹球和球拍构成的游戏。球会在屏幕上飞过来,玩家要用球拍把它弹回去。如果球落到了屏幕底部,那么游戏就结束了。

13.2 创建游戏画布

from tkinter import *
import random
import time

tk = Tk()
tk.title('Game')
# resizable函数是窗口的大小不可调整
# 其中参数(0,0)的意思是窗口的大小在水平方向和垂直方向上都不能改变
tk.resizable(0,0)
# wm_attributes告诉tkinter把包含我们画布的窗口放到所有其他窗口之前(-topmost)
tk.wm_attributes('-topmost',1)
# bd=0和highlightthickness=0确保画布之外没有边框
canvas = Canvas(tk,width=500,height=400,bd=0,highlightthickness=0)
canvas.pack()
# tk.update()让tkinter为我们游戏中的动画做好初始化
# 如果没有这一行,我们看到的东西会和期望的不一样
tk.update()

resizable函数是窗口的大小不可调整,其中参数(0,0)的意思是窗口的大小在水平方向和垂直方向上都不能改变。wm_attributes告诉tkinter把包含我们画布的窗口放到所有其他窗口之前(-topmost)。bd=0和highlightthickness=0确保画布之外没有边框。tk.update()让tkinter为我们游戏中的动画做好初始化,如果没有这一行,我们看到的东西会和期望的不一样

13.3 创建Ball类

思路:

  1. 创建一个叫Ball的类,它有两个参数,一个是画布,一个是求的颜色。
  2. 把画布保存到一个对象变量中,因为我们会在它上面画球。
  3. 在画布上画一个用颜色参数作为填充色的小球。
  4. 把tkinter画小球时所返回的ID保存起来,因为我们要用它来移动屏幕上的小球。

承接13.2中的代码:

from tkinter import *
import random
import time

class Ball:
    def __init__(self,canvas,color):
        self.canvas = canvas
        # create_oval函数中,(10,10)左上角坐标,(25,25)右下角坐标
        self.id = canvas.create_oval(10,10,25,25,fill=color)
        # 把椭圆形移到画布的中心(245,100)
        # 画布之所以知道要移动什么,是因为我们用保存好的形状ID来标识它
        self.canvas.move(self.id,245,100)

    def draw(self):
        pass
        
tk = Tk()
tk.title('Game')
# resizable函数是窗口的大小不可调整
# 其中参数(0,0)的意思是窗口的大小在水平方向和垂直方向上都不能改变
tk.resizable(0,0)
# wm_attributes告诉tkinter把包含我们画布的窗口放到所有其他窗口之前(-topmost)
tk.wm_attributes('-topmost',1)
# bd=0和highlightthickness=0确保画布之外没有边框
canvas = Canvas(tk,width=500,height=400,bd=0,highlightthickness=0)
canvas.pack()
# tk.update()让tkinter为我们游戏中的动画做好初始化
# 如果没有这一行,我们看到的东西会和期望的不一样
tk.update()

# 创建红色小球对象
ball = Ball(canvas,'red')

# 防止窗口马上关闭,增加一个动画循环,我们把它成为我们游戏的“主循环”
# 这个主循环目前只是不停地让tkinter重画屏幕,然后休息百分之一秒
while 1:
    tk.update_idletasks()
    tk.update()
    time.sleep(0.01)

在关闭游戏窗口时,你可能会见到Shell程序中打印出错误信息。这是因为当你关闭窗口时,代码要强行从while循环中跳出来,Python觉得不爽。

13.4 增加几个动作

13.4.1 让小球移动

修改draw函数,并在主循环中调用

from tkinter import *
import random
import time

class Ball:
    def __init__(self,canvas,color):
        self.canvas = canvas
        # create_oval函数中,(10,10)左上角坐标,(25,25)右下角坐标
        self.id = canvas.create_oval(10,10,25,25,fill=color)
        # 把椭圆形移到画布的中心(245,100)
        # 画布之所以知道要移动什么,是因为我们用保存好的形状ID来标识它
        self.canvas.move(self.id,245,100)

    def draw(self):
        # id是椭圆形的id,0是不要水平移动,-1是在屏幕上向上移动1个像素
        self.canvas.move(self.id,0,-1)
        
tk = Tk()
tk.title('Game')
# resizable函数是窗口的大小不可调整
# 其中参数(0,0)的意思是窗口的大小在水平方向和垂直方向上都不能改变
tk.resizable(0,0)
# wm_attributes告诉tkinter把包含我们画布的窗口放到所有其他窗口之前(-topmost)
tk.wm_attributes('-topmost',1)
# bd=0和highlightthickness=0确保画布之外没有边框
canvas = Canvas(tk,width=500,height=400,bd=0,highlightthickness=0)
canvas.pack()
# tk.update()让tkinter为我们游戏中的动画做好初始化
# 如果没有这一行,我们看到的东西会和期望的不一样
tk.update()

# 创建红色小球对象
ball = Ball(canvas,'red')

# 防止窗口马上关闭,增加一个动画循环,我们把它成为我们游戏的“主循环”
# 这个主循环目前只是不停地让tkinter重画屏幕,然后休息百分之一秒
while 1:
    ball.draw()
    tk.update_idletasks()
    tk.update()
    time.sleep(0.01)

在关闭游戏窗口时,你可能会见到Shell程序中打印出错误信息。这是因为当你关闭窗口时,代码要强行从while循环中跳出来,Python觉得不爽。

13.4.2 让小球来回反弹
from tkinter import *
import random
import time

class Ball:
    def __init__(self,canvas,color):
        self.canvas = canvas
        # create_oval函数中,(10,10)左上角坐标,(25,25)右下角坐标
        self.id = canvas.create_oval(10,10,25,25,fill=color)
        # 把椭圆形移到画布的中心(245,100)
        # 画布之所以知道要移动什么,是因为我们用保存好的形状ID来标识它
        self.canvas.move(self.id,245,100)
        self.x = 0
        self.y = -1
        # winfo_height函数用来获取画布当前的高度
        self.canvas_height = self.canvas.winfo_height()

    def draw(self):
        # id是椭圆形的id,0是不要水平移动,-1是在屏幕上向上移动1个像素
        #self.canvas.move(self.id,0,-1)
        self.canvas.move(self.id,self.x,self.y)
        # coords函数返回一个由四个数字组成的列表来表示坐标。
        # 比如:[255.0, 29.0, 270.0, 44.0]
        # 前两个数字是椭圆形左上角的坐标(x1,y1),后两个数字是椭圆形右下角的坐标(x2,y2)
        pos = self.canvas.coords(self.id)
        # y1=0 说明小球撞到了屏幕顶部,接需要向下移动了,故使self.y=1
        if pos[1] <= 0:
            self.y = 1
        # y2=self.canvas_height 说明小球撞到了屏幕底部,接需要向上移动了,故使self.y=-1
        if pos[3] >= self.canvas_height:
            self.y = -1
        
tk = Tk()
tk.title('Game')
# resizable函数是窗口的大小不可调整
# 其中参数(0,0)的意思是窗口的大小在水平方向和垂直方向上都不能改变
tk.resizable(0,0)
# wm_attributes告诉tkinter把包含我们画布的窗口放到所有其他窗口之前(-topmost)
tk.wm_attributes('-topmost',1)
# bd=0和highlightthickness=0确保画布之外没有边框
canvas = Canvas(tk,width=500,height=400,bd=0,highlightthickness=0)
canvas.pack()
# tk.update()让tkinter为我们游戏中的动画做好初始化
# 如果没有这一行,我们看到的东西会和期望的不一样
tk.update()

# 创建红色小球对象
ball = Ball(canvas,'red')

# 防止窗口马上关闭,增加一个动画循环,我们把它成为我们游戏的“主循环”
# 这个主循环目前只是不停地让tkinter重画屏幕,然后休息百分之一秒
while 1:
    ball.draw()
    tk.update_idletasks()
    tk.update()
    time.sleep(0.01)

coords函数返回一个由四个数字组成的列表来表示坐标。print(self.canvas.coords(self.id)) 打印出来可以看到返回值[255.0, 29.0, 270.0, 44.0],前两个数字是椭圆形左上角的坐标(x1,y1),后两个数字是椭圆形右下角的坐标(x2,y2)

13.4.3 改变小球的起始方向
from tkinter import *
import random
import time

class Ball:
    def __init__(self,canvas,color):
        self.canvas = canvas
        # create_oval函数中,(10,10)左上角坐标,(25,25)右下角坐标
        self.id = canvas.create_oval(10,10,25,25,fill=color)
        # 把椭圆形移到画布的中心(245,100)
        # 画布之所以知道要移动什么,是因为我们用保存好的形状ID来标识它
        self.canvas.move(self.id,245,100)
        starts = [-3,-2,-1,1,2,3]
        random.shuffle(starts)
        # x有可能是列表中的任意一个值,从-3到3
        self.x = starts[0]
        # 把y改成-3,让小球飞快一点
        self.y = -3
        # winfo_height函数用来获取画布当前的高度
        self.canvas_height = self.canvas.winfo_height()
        # winfo_width函数用来获取画布当前的宽度
        self.canvas_width = self.canvas.winfo_width()

    def draw(self):
        # id是椭圆形的id,0是不要水平移动,-1是在屏幕上向上移动1个像素
        #self.canvas.move(self.id,0,-1)
        self.canvas.move(self.id,self.x,self.y)
        # coords函数返回一个由四个数字组成的列表来表示坐标。
        # 比如:[255.0, 29.0, 270.0, 44.0]
        # 前两个数字是椭圆形左上角的坐标(x1,y1),后两个数字是椭圆形右下角的坐标(x2,y2)
        pos = self.canvas.coords(self.id)
        # y1=0 说明小球撞到了屏幕顶部,接下来需要向下移动了,故使self.y=3
        if pos[1] <= 0:
            self.y = 3
        # y2=self.canvas_height 说明小球撞到了屏幕底部,接下来需要向上移动了,故使self.y=-3
        if pos[3] >= self.canvas_height:
            self.y = -3
        # x1=0 说明小球撞到了屏幕最左端,接下来需要向右移动了,故使self.x=3
        if pos[0] <= 0:
            self.x = 3
        # x2=0 说明小球撞到了屏幕最右端,接下来需要向左移动了,故使self.x=-3
        if pos[2] >= self.canvas_width:
            self.x = -3
        
tk = Tk()
tk.title('Game')
# resizable函数是窗口的大小不可调整
# 其中参数(0,0)的意思是窗口的大小在水平方向和垂直方向上都不能改变
tk.resizable(0,0)
# wm_attributes告诉tkinter把包含我们画布的窗口放到所有其他窗口之前(-topmost)
tk.wm_attributes('-topmost',1)
# bd=0和highlightthickness=0确保画布之外没有边框
canvas = Canvas(tk,width=500,height=400,bd=0,highlightthickness=0)
canvas.pack()
# tk.update()让tkinter为我们游戏中的动画做好初始化
# 如果没有这一行,我们看到的东西会和期望的不一样
tk.update()

# 创建红色小球对象
ball = Ball(canvas,'red')

# 防止窗口马上关闭,增加一个动画循环,我们把它成为我们游戏的“主循环”
# 这个主循环目前只是不停地让tkinter重画屏幕,然后休息百分之一秒
while 1:
    ball.draw()
    tk.update_idletasks()
    tk.update()
    time.sleep(0.01)

第14章 完成你的第一个游戏:反弹吧,小球!

14.1 加上球拍

from tkinter import *
import random
import time

class Ball:
    def __init__(self,canvas,color):
        self.canvas = canvas
        # create_oval函数中,(10,10)左上角坐标,(25,25)右下角坐标
        self.id = canvas.create_oval(10,10,25,25,fill=color)
        # 把椭圆形移到画布的中心(245,100)
        # 画布之所以知道要移动什么,是因为我们用保存好的形状ID来标识它
        self.canvas.move(self.id,245,100)
        starts = [-3,-2,-1,1,2,3]
        random.shuffle(starts)
        # x有可能是列表中的任意一个值,从-3到3
        self.x = starts[0]
        # 把y改成-3,让小球飞快一点
        self.y = -3
        # winfo_height函数用来获取画布当前的高度
        self.canvas_height = self.canvas.winfo_height()
        # winfo_width函数用来获取画布当前的宽度
        self.canvas_width = self.canvas.winfo_width()

    def draw(self):
        # id是椭圆形的id,0是不要水平移动,-1是在屏幕上向上移动1个像素
        #self.canvas.move(self.id,0,-1)
        self.canvas.move(self.id,self.x,self.y)
        # coords函数返回一个由四个数字组成的列表来表示坐标。
        # 比如:[255.0, 29.0, 270.0, 44.0]
        # 前两个数字是椭圆形左上角的坐标(x1,y1),后两个数字是椭圆形右下角的坐标(x2,y2)
        pos = self.canvas.coords(self.id)
        # y1=0 说明小球撞到了屏幕顶部,接下来需要向下移动了,故使self.y=3
        if pos[1] <= 0:
            self.y = 3
        # y2=self.canvas_height 说明小球撞到了屏幕底部,接下来需要向上移动了,故使self.y=-3
        if pos[3] >= self.canvas_height:
            self.y = -3
        # x1=0 说明小球撞到了屏幕最左端,接下来需要向右移动了,故使self.x=3
        if pos[0] <= 0:
            self.x = 3
        # x2=0 说明小球撞到了屏幕最右端,接下来需要向左移动了,故使self.x=-3
        if pos[2] >= self.canvas_width:
            self.x = -3

class Paddle:
    def __init__(self,canvas,color):
        self.canvas = canvas
        self.id = canvas.create_rectangle(0,0,100,10,fill=color)
        self.canvas.move(self.id,200,300)

    def draw(self):
        pass
        
tk = Tk()
tk.title('Game')
# resizable函数是窗口的大小不可调整
# 其中参数(0,0)的意思是窗口的大小在水平方向和垂直方向上都不能改变
tk.resizable(0,0)
# wm_attributes告诉tkinter把包含我们画布的窗口放到所有其他窗口之前(-topmost)
tk.wm_attributes('-topmost',1)
# bd=0和highlightthickness=0确保画布之外没有边框
canvas = Canvas(tk,width=500,height=400,bd=0,highlightthickness=0)
canvas.pack()
# tk.update()让tkinter为我们游戏中的动画做好初始化
# 如果没有这一行,我们看到的东西会和期望的不一样
tk.update()

# 创建红色小球对象
ball = Ball(canvas,'red')
# 创建蓝色球拍
paddle = Paddle(canvas,'blue')

# 防止窗口马上关闭,增加一个动画循环,我们把它成为我们游戏的“主循环”
# 这个主循环目前只是不停地让tkinter重画屏幕,然后休息百分之一秒
while 1:
    ball.draw()
    paddle.draw()
    tk.update_idletasks()
    tk.update()
    time.sleep(0.01)

14.2 让球拍移动

要想让球拍左右移动,我们要用事件绑定来把左右方向键绑定到Paddle类的新函数上。当玩家按向左键时,变量x会被设置为-2(向左移),按向右键时,变量x会被设置为2(向右移)。

from tkinter import *
import random
import time

class Ball:
    def __init__(self,canvas,color):
        self.canvas = canvas
        # create_oval函数中,(10,10)左上角坐标,(25,25)右下角坐标
        self.id = canvas.create_oval(10,10,25,25,fill=color)
        # 把椭圆形移到画布的中心(245,100)
        # 画布之所以知道要移动什么,是因为我们用保存好的形状ID来标识它
        self.canvas.move(self.id,245,100)
        starts = [-3,-2,-1,1,2,3]
        random.shuffle(starts)
        # x有可能是列表中的任意一个值,从-3到3
        self.x = starts[0]
        # 把y改成-3,让小球飞快一点
        self.y = -3
        # winfo_height函数用来获取画布当前的高度
        self.canvas_height = self.canvas.winfo_height()
        # winfo_width函数用来获取画布当前的宽度
        self.canvas_width = self.canvas.winfo_width()

    def draw(self):
        # id是椭圆形的id,0是不要水平移动,-1是在屏幕上向上移动1个像素
        #self.canvas.move(self.id,0,-1)
        self.canvas.move(self.id,self.x,self.y)
        # coords函数返回一个由四个数字组成的列表来表示坐标。
        # 比如:[255.0, 29.0, 270.0, 44.0]
        # 前两个数字是椭圆形左上角的坐标(x1,y1),后两个数字是椭圆形右下角的坐标(x2,y2)
        pos = self.canvas.coords(self.id)
        # y1=0 说明小球撞到了屏幕顶部,接下来需要向下移动了,故使self.y=3
        if pos[1] <= 0:
            self.y = 3
        # y2=self.canvas_height 说明小球撞到了屏幕底部,接下来需要向上移动了,故使self.y=-3
        if pos[3] >= self.canvas_height:
            self.y = -3
        # x1=0 说明小球撞到了屏幕最左端,接下来需要向右移动了,故使self.x=3
        if pos[0] <= 0:
            self.x = 3
        # x2=0 说明小球撞到了屏幕最右端,接下来需要向左移动了,故使self.x=-3
        if pos[2] >= self.canvas_width:
            self.x = -3

class Paddle:
    def __init__(self,canvas,color):
        self.canvas = canvas
        self.id = canvas.create_rectangle(0,0,100,10,fill=color)
        self.canvas.move(self.id,200,300)
        self.x = 0
        # winfo_width函数用来获取画布当前的宽度
        self.canvas_width = self.canvas.winfo_width()
        # 绑定向左键、向右键的按下事件
        self.canvas.bind_all('<KeyPress-Left>',self.turn_left)
        self.canvas.bind_all('<KeyPress-Right>',self.turn_right)

    def draw(self):
        self.canvas.move(self.id,self.x,0)
        pos = self.canvas.coords(self.id)
        if pos[0] <= 0:            
            self.x = 0
        elif pos[2] >= self.canvas_width:
            self.x = 0

    def turn_left(self, evt):
        self.x = -2

    def turn_right(self, evt):
        self.x = 2
        
tk = Tk()
tk.title('Game')
# resizable函数是窗口的大小不可调整
# 其中参数(0,0)的意思是窗口的大小在水平方向和垂直方向上都不能改变
tk.resizable(0,0)
# wm_attributes告诉tkinter把包含我们画布的窗口放到所有其他窗口之前(-topmost)
tk.wm_attributes('-topmost',1)
# bd=0和highlightthickness=0确保画布之外没有边框
canvas = Canvas(tk,width=500,height=400,bd=0,highlightthickness=0)
canvas.pack()
# tk.update()让tkinter为我们游戏中的动画做好初始化
# 如果没有这一行,我们看到的东西会和期望的不一样
tk.update()

# 创建红色小球对象
ball = Ball(canvas,'red')
# 创建蓝色球拍
paddle = Paddle(canvas,'blue')

# 防止窗口马上关闭,增加一个动画循环,我们把它成为我们游戏的“主循环”
# 这个主循环目前只是不停地让tkinter重画屏幕,然后休息百分之一秒
while 1:
    ball.draw()
    paddle.draw()
    tk.update_idletasks()
    tk.update()
    time.sleep(0.01)

14.3 判断小球是否击中球拍

from tkinter import *
import random
import time

class Ball:
    def __init__(self,canvas,paddle,color):
        self.canvas = canvas
        self.paddle = paddle
        # create_oval函数中,(10,10)左上角坐标,(25,25)右下角坐标
        self.id = canvas.create_oval(10,10,25,25,fill=color)
        # 把椭圆形移到画布的中心(245,100)
        # 画布之所以知道要移动什么,是因为我们用保存好的形状ID来标识它
        self.canvas.move(self.id,245,100)
        starts = [-3,-2,-1,1,2,3]
        random.shuffle(starts)
        # x有可能是列表中的任意一个值,从-3到3
        self.x = starts[0]
        # 把y改成-3,让小球飞快一点
        self.y = -3
        # winfo_height函数用来获取画布当前的高度
        self.canvas_height = self.canvas.winfo_height()
        # winfo_width函数用来获取画布当前的宽度
        self.canvas_width = self.canvas.winfo_width()

    def draw(self):
        # id是椭圆形的id,0是不要水平移动,-1是在屏幕上向上移动1个像素
        #self.canvas.move(self.id,0,-1)
        self.canvas.move(self.id,self.x,self.y)
        # coords函数返回一个由四个数字组成的列表来表示坐标。
        # 比如:[255.0, 29.0, 270.0, 44.0]
        # 前两个数字是椭圆形左上角的坐标(x1,y1),后两个数字是椭圆形右下角的坐标(x2,y2)
        pos = self.canvas.coords(self.id)
        # y1=0 说明小球撞到了屏幕顶部,接下来需要向下移动了,故使self.y=3
        if pos[1] <= 0:
            self.y = 3
        # y2=self.canvas_height 说明小球撞到了屏幕底部,接下来需要向上移动了,故使self.y=-3
        if pos[3] >= self.canvas_height:
            self.y = -3
        if self.hit_paddle(pos) == True:
            self.y = -3
        # x1=0 说明小球撞到了屏幕最左端,接下来需要向右移动了,故使self.x=3
        if pos[0] <= 0:
            self.x = 3
        # x2=0 说明小球撞到了屏幕最右端,接下来需要向左移动了,故使self.x=-3
        if pos[2] >= self.canvas_width:
            self.x = -3

    def hit_paddle(self,pos):
        paddle_pos = self.canvas.coords(self.paddle.id)
        # 如果小球的右侧大于球拍的左侧,并且小球的左侧小于球拍的右侧
        if pos[2] >= paddle_pos[0] and pos[0] <= paddle_pos[2]:
            # 小球的底部(pos[3])在球拍的顶部(paddle_pos[1])和底部(paddle_pos[3])之间
            if pos[3] >= paddle_pos[1] and pos[3] <= paddle_pos[3]:
                return True
        return False

class Paddle:
    def __init__(self,canvas,color):
        self.canvas = canvas
        self.id = canvas.create_rectangle(0,0,100,10,fill=color)
        self.canvas.move(self.id,200,300)
        self.x = 0
        # winfo_width函数用来获取画布当前的宽度
        self.canvas_width = self.canvas.winfo_width()
        # 绑定向左键、向右键的按下事件
        self.canvas.bind_all('<KeyPress-Left>',self.turn_left)
        self.canvas.bind_all('<KeyPress-Right>',self.turn_right)

    def draw(self):
        self.canvas.move(self.id,self.x,0)
        pos = self.canvas.coords(self.id)
        if pos[0] <= 0:            
            self.x = 0
        elif pos[2] >= self.canvas_width:
            self.x = 0

    def turn_left(self, evt):
        self.x = -2

    def turn_right(self, evt):
        self.x = 2
        
tk = Tk()
tk.title('Game')
# resizable函数是窗口的大小不可调整
# 其中参数(0,0)的意思是窗口的大小在水平方向和垂直方向上都不能改变
tk.resizable(0,0)
# wm_attributes告诉tkinter把包含我们画布的窗口放到所有其他窗口之前(-topmost)
tk.wm_attributes('-topmost',1)
# bd=0和highlightthickness=0确保画布之外没有边框
canvas = Canvas(tk,width=500,height=400,bd=0,highlightthickness=0)
canvas.pack()
# tk.update()让tkinter为我们游戏中的动画做好初始化
# 如果没有这一行,我们看到的东西会和期望的不一样
tk.update()

# 创建蓝色球拍
paddle = Paddle(canvas,'blue')
# 创建红色小球对象
ball = Ball(canvas,paddle,'red')


# 防止窗口马上关闭,增加一个动画循环,我们把它成为我们游戏的“主循环”
# 这个主循环目前只是不停地让tkinter重画屏幕,然后休息百分之一秒
while 1:
    ball.draw()
    paddle.draw()
    tk.update_idletasks()
    tk.update()
    time.sleep(0.01)
    

image
clipboard.png

为什么要看小球的底部是否在球拍的顶部和底部之间,而不是直接判断小球的底部是否打到了球拍的顶部?因为小球在屏幕上每次移动3个 像素,如果我们只检查了小球是否到达了球拍的顶部,我们可能已经跨过了那个位置,这样的话小球仍会向前移动,穿过球拍,不会停止。

14.3 增加输赢因素

小球落到屏幕底部时,Game over

from tkinter import *
import random
import time

class Ball:
    def __init__(self,canvas,paddle,color):
        self.canvas = canvas
        self.paddle = paddle
        # create_oval函数中,(10,10)左上角坐标,(25,25)右下角坐标
        self.id = canvas.create_oval(10,10,25,25,fill=color)
        # 把椭圆形移到画布的中心(245,100)
        # 画布之所以知道要移动什么,是因为我们用保存好的形状ID来标识它
        self.canvas.move(self.id,245,100)
        starts = [-3,-2,-1,1,2,3]
        random.shuffle(starts)
        # x有可能是列表中的任意一个值,从-3到3
        self.x = starts[0]
        # 把y改成-3,让小球飞快一点
        self.y = -3
        # winfo_height函数用来获取画布当前的高度
        self.canvas_height = self.canvas.winfo_height()
        # winfo_width函数用来获取画布当前的宽度
        self.canvas_width = self.canvas.winfo_width()
        self.hit_bottom = False

    def draw(self):
        # id是椭圆形的id,0是不要水平移动,-1是在屏幕上向上移动1个像素
        #self.canvas.move(self.id,0,-1)
        self.canvas.move(self.id,self.x,self.y)
        # coords函数返回一个由四个数字组成的列表来表示坐标。
        # 比如:[255.0, 29.0, 270.0, 44.0]
        # 前两个数字是椭圆形左上角的坐标(x1,y1),后两个数字是椭圆形右下角的坐标(x2,y2)
        pos = self.canvas.coords(self.id)
        # y1=0 说明小球撞到了屏幕顶部,接下来需要向下移动了,故使self.y=3
        if pos[1] <= 0:
            self.y = 3            
        # y2=self.canvas_height 说明小球撞到了屏幕底部
        if pos[3] >= self.canvas_height:
            #self.y = -3
            self.hit_bottom = True
        if self.hit_paddle(pos) == True:
            self.y = -3
        # x1=0 说明小球撞到了屏幕最左端,接下来需要向右移动了,故使self.x=3
        if pos[0] <= 0:
            self.x = 3
        # x2=0 说明小球撞到了屏幕最右端,接下来需要向左移动了,故使self.x=-3
        if pos[2] >= self.canvas_width:
            self.x = -3

    def hit_paddle(self,pos):
        paddle_pos = self.canvas.coords(self.paddle.id)
        # 如果小球的右侧大于球拍的左侧,并且小球的左侧小于球拍的右侧
        if pos[2] >= paddle_pos[0] and pos[0] <= paddle_pos[2]:
            # 小球的底部(pos[3])在球拍的顶部(paddle_pos[1])和底部(paddle_pos[3])之间
            if pos[3] >= paddle_pos[1] and pos[3] <= paddle_pos[3]:
                return True
        return False

class Paddle:
    def __init__(self,canvas,color):
        self.canvas = canvas
        self.id = canvas.create_rectangle(0,0,100,10,fill=color)
        self.canvas.move(self.id,200,300)
        self.x = 0
        # winfo_width函数用来获取画布当前的宽度
        self.canvas_width = self.canvas.winfo_width()
        # 绑定向左键、向右键的按下事件
        self.canvas.bind_all('<KeyPress-Left>',self.turn_left)
        self.canvas.bind_all('<KeyPress-Right>',self.turn_right)

    def draw(self):
        self.canvas.move(self.id,self.x,0)
        pos = self.canvas.coords(self.id)
        if pos[0] <= 0:            
            self.x = 0
        elif pos[2] >= self.canvas_width:
            self.x = 0

    def turn_left(self, evt):
        self.x = -2

    def turn_right(self, evt):
        self.x = 2
        
tk = Tk()
tk.title('Game')
# resizable函数是窗口的大小不可调整
# 其中参数(0,0)的意思是窗口的大小在水平方向和垂直方向上都不能改变
tk.resizable(0,0)
# wm_attributes告诉tkinter把包含我们画布的窗口放到所有其他窗口之前(-topmost)
tk.wm_attributes('-topmost',1)
# bd=0和highlightthickness=0确保画布之外没有边框
canvas = Canvas(tk,width=500,height=400,bd=0,highlightthickness=0)
canvas.pack()
# tk.update()让tkinter为我们游戏中的动画做好初始化
# 如果没有这一行,我们看到的东西会和期望的不一样
tk.update()

# 创建蓝色球拍
paddle = Paddle(canvas,'blue')
# 创建红色小球对象
ball = Ball(canvas,paddle,'red')


# 防止窗口马上关闭,增加一个动画循环,我们把它成为我们游戏的“主循环”
# 这个主循环目前只是不停地让tkinter重画屏幕,然后休息百分之一秒
while 1:
    if ball.hit_bottom == False:
        ball.draw()
        paddle.draw()
    tk.update_idletasks()
    tk.update()
    time.sleep(0.01)