唯一作业:可回放的点彩画板
拆解任务,计划按以下顺序逐一攻破:
本地运行codeskulptor,不成功。先放一边,以后再试。
把课程的代码模板复制过来,帮助理清思路:
import simplegui
# helper function
# define event handlers
def mouseclick(pos):
pass
# create frame and controls
frame = simplegui.create_frame("Palette", 800, 100)
# register event handlers
frame.set_mouseclick_handler(mouseclick)
frame.set_draw_handler(draw)
# get things rolling
frame.start()
# Always remember to review the grading rubric
没思路?不必完全从头开始,可以先看课程栗子,复习如何用鼠标画圆。
在文档中找到画圆的语法:
canvas.draw_circle(center_point, radius, line_width, line_color, fill_color = color)
空想当然是想不出来的,于是:
于是去偷看同学作业:
pos = [(x, y-n),(x+n,y+n/2),(x-n,y+n/2)]
说明应该是给三个点的坐标画出三角形。
这时发现自己对 pos 不理解,于是回看课程 Mouse input ,找到文档中 SimpleGUI Module — Control Objects - Set the Mouse Input Handler 查看:
def mouse_handler(position):
…
这里的参数是一对数字(怎么觉得有点坑)。如果只需要用到点坐标,直接用变量读到坐标就能调用了:
point_pos = list(pos)
但这里需要取到x和y的值,用来计算三角形的三个点的坐标。
鼠标位置只有两个点的坐标,怎么画出三个点的三角形?
还是迷迷糊糊,直到画了个图来帮助理解:
尝试将鼠标的坐标转换为三角形的三个顶点并绘制出来:
def mouseclick(pos):
global circle_pos,triangle_pos
circle_pos = list(pos)
x = pos[0]
y = pos[1]
triangle_pos = [[x, y-tri_height],[x+2*tri_height,y+tri_height],[x-2*tri_height,y+tri_height]]
def draw(canvas):
canvas.draw_circle(circle_pos, BALL_RADIUS, 1, "Black")
canvas.draw_polygon(triangle_pos, 1, "Black")
但是程序提示出错 undefined。改成分别定义三角形的三个坐标,用draw_polygon可以画出来。
canvas.draw_polygon([triangle_1,triangle_2,triangle_3], 1, "Black")
为什么??
原理同三角形
怎样把每一次绘制的位置、形状、颜色保存到一个数组里面?
参考课程
def click(pos):
ball_list.append(pos) //记录每个位置
def draw(canvas):
for ball_pos in ball_list:
canvas.draw_circle(ball_pos, ball_radius, 1, "Black", ball_color)
用 .append(pos) 存放每次的坐标位置,然后在draw函数中用for循环将list里面的位置全部绘制出来。
可以先进行圆形/三角形/正方形的判断,然后存入list中:
shape_list.append(pos)
在画布中给出三个形状的按钮,然后定义各自的 event handler。在draw函数中用if判断绘制圆形还是多边形(三角形和正方形)。按这个思路写完会报错,在圆形和多边形切换的时候,问题出在记录的pos进行 2点/3点 的切换。(上面画三角形时的疑问又出来了)
def mouseclick(pos):
global ShapeType,shape_list,Radius
x = pos[0]
y = pos[1]
if ShapeType == "circle":
pos = [x,y]
elif ShapeType == "triangle":
pos = [(x, y - Radius),(x + 2*Radius, y + Radius),(x - 2*Radius, y + Radius)]
elif ShapeType == "square":
pos = [(x - Radius , y - Radius),(x + Radius , y - Radius),(x + Radius , y + Radius),(x - Radius , y + Radius)]
shape_list.append(pos)
def draw(canvas):
global ShapeType,shape_list,Radius
for shape_pos in shape_list:
if ShapeType == "circle":
canvas.draw_circle(shape_pos,Radius, 1, "Black")
else:
canvas.draw_polygon(shape_pos, 1, "Black")
报错TypeError: center must be a 2 element sequence
。
猜想是记录每个形状时,仅仅记录了位置信息 shape_list.append(pos)
,draw函数里面用来判断形状的是全局变量,这个变量并没有记录到历史中,所以切换形状时,三个点和两个点的情况无法兼容。
解决办法是把形状也记录到历史中。
参考了同学的作业,修改如下:
def mouseclick(pos):
global ShapeType,shape_list,Radius
x = pos[0]
y = pos[1]
if ShapeType == "circle":
pos = [x,y]
elif ShapeType == "triangle":
pos = [(x, y - Radius),(x + 2*Radius, y + Radius),(x - 2*Radius, y + Radius)]
elif ShapeType == "square":
pos = [(x - Radius , y - Radius),(x + Radius , y - Radius),(x + Radius , y + Radius),(x - Radius , y + Radius)]
print pos
shape_list.append[pos,ShapeType]
def draw(canvas):
global ShapeType,shape_list,Radius
for shapes in shape_list:
if shapes[0] == "circle":
canvas.draw_circle(shapes[0],Radius, 1, "Black")
else:
canvas.draw_polygon(shapes[0], 1, "Black")
运行时对这行shape_list.append[pos,ShapeType]
报错:TypeError: '<invalid type>' does not support indexing
。看来是用错了[]。改成 () 后报错TypeError: append() takes exactly 2 arguments (3 given)
。嗯哪,对list还是不太明白,滚去看文档 Types & Operations: list用 [] 没错,但append应该用 (),append内用 () 还是 [] ?
code | output |
---|---|
a_list = [1, 2, 3] | [1, 2, 3, 4, [5, 6, 7]] |
a_list.append(4) | |
a_list.append([5, 6, 7]) | |
print a_list |
so,append里面每个记录应该用[]。
修改后继续报错 - - 。检查代码后发现 if shapes[0] == "circle":
调用错了list中shape的位置,修改0为1。终于把三种图形画出来了。
思路自然是像坐标一样记录到list中,但是这样写完,运行结果是更换颜色后,所有点的颜色都跟着变了。
def draw(canvas):
global ShapeType,ShapeColor,shape_list,Radius
for shapes in shape_list:
if shapes[1] == "circle":
canvas.draw_circle(shapes[0],Radius, 1, "Black",ShapeColor)
else:
canvas.draw_polygon(shapes[0], 1, "Black",ShapeColor)
问题出在draw函数中,用了全局的ShapeColor,而不是shape_list中的存放的颜色记录。
修改成以下代码后文件解决:
if shapes[1] == "circle":
canvas.draw_circle(shapes[0],Radius, 1, "Black",shapes[2])
else:
canvas.draw_polygon(shapes[0], 1, "Black",shapes[2])
未实现
未实现
每click(还是draw)一次,计数器+1。
if clickCount < 1025 :
shape_list.append([pos,ShapeType,ShapeColor])
clickCount += 1
画布上需要给出当前点击计数,不然不知道点了多少次。。
canvas.draw_text(clickCount,(10,10),12,"green","Verdana")
错误提示:TypeError: text must be a string
,又忘记转为字符串了。
canvas.draw_text(str(clickCount)+"st click" ,(10,10),12,"black")
Done.
之前有同学剧透是用timer控制。先去doc里面查了查timer用法:
Create Timersimplegui.create_timer()
Start Timertimer.start()
Stop Timer
不太有思路,找了例子 Screensaver 来看。timer的两个关键要素:间隔和动作。间隔容易处理,那么希望间隔后做什么事情?一次间隔画出记录中的一步。
画出记录意味着从list中读取历史
问题来了,既要读取第几步,又要读取这一步的参数,咋整?list是二维的吗?
简单查了一下,list支持二维,尝试写下:
def replayStep():
global clickCount,Radius
if clickCount > 0 :
if shape_list[clickCount-1][1] == "circle":
canvas.draw_circle(shape_list[clickCount],Radius,1,"Black",shape_list[clickCount][2] )
else :
canvas.draw_polygon(shape_list[clickCount],1,"Black",shape_list[clickCount][2] )
clickCount -= 1
提示是 NameError: name 'canvas' is not defined
,看来 canvas.draw 不能随便调用。此路不通。
又偷看了同学的作业,思路是在回放函数中整个重绘。
之前思路的问题,过于相信程序的强大,想当然地认为回放过的记录会保留在画布中,每次间隔后只绘制下一个点。其实,每次绘制的步骤都是:将需要绘制的点全部绘制一遍。因为有timer的间隔,所以看上去是新增了最近的一个点。于是画布干的事情是每次间隔后清空整个画布并全量重新绘制。(又或许确实有优雅的方法只绘制增量?)
再理一下步骤:
按上面的思路写出:
def replayStep():
global clickCount,shape_list,history_list
shape_list = [] #清空
if step < clickCount :
step += 1
for history_step in range(0,step):
shape_list.append(history_list[history_step])
else:
step = 0
期间经历的错误包括:
:
结尾range(0,history_step)
自循环当然,最致命的错误,是没有将step定义为全局变量,导致提示 undefined: Error: local variable 'step' referenced before assignment
。开始还尝试定义局部变量解决这个问题,运行结果是一旦开始回放,就把之前绘制的图形清空到只剩下最初的那一个。说明记录并没有被保存。检查了很久没有发现问题,再次偷看同学作业,终于发现全局变量的问题。
将step定义为全局变量后,问题解决。
通过设置 timer 的 interval 实现速度调节
这些写出来后,发现运行无效,也不报错。
def speedup():
global interval
interval += 500
def slowdown():
global interval
interval -= 500
frame.add_button("加快0.5秒\n",speedup,100)
frame.add_button("减慢0.5秒\n",slowdown,100)
timer = simplegui.create_timer(interval,replayStep)
不报错说明语法没问题,不生效说明还是按之前的 timer 设定去执行。所以应该再次 start timer?
def speedup():
global interval
interval += 500
timer = simplegui.create_timer(interval,replayStep)
timer.start()
def slowdown():
global interval
interval -= 500
timer = simplegui.create_timer(interval,replayStep)
timer.start()
修改后运行,发现回放时每个点的间隔不一样,按了减慢按钮后反而加快了 - -
再次偷看作业后,发现需要先暂停已经在运行的 timer 。
def speedup():
global interval #,timer
if timer.is_running():
timer.stop()
interval += 500
timer = simplegui.create_timer(interval,replayStep)
timer.start()
修改后运行报错 undefined: Error: local variable 'timer' referenced before assignment
,原来 timer 是个变量,需要定义为全局变量呃。。。
修改后运行发现两个问题:
于是将间隔修改为 加速-原间隔/2,减速-原间隔*2 ,问题解决。
未实现