课程全名:An Introduction to Interactive Programming in Python,来自 Rice University
授课教授:Joe Warren, Scott Rixner, John Greiner, Stephen Wong
工具:http://www.codeskulptor.org/, simplegui 模块
最后一个project,继续完善上一周的工程,做完就是一个既简单又棒棒的打陨石(飞机)游戏。
第八周:
关于Python的知识,set类型的用法
定义:set和C++中的STL中的set类似,用来维护一组不重复的无序元素。
# 定义一个空的set
set_empty = set() # or set([])
print set_empty
# set([]) # 定义set
set_a = set([1, 2, 3])
set_b = set((1, 2, 3))
set_c = set({1:1, 2:1, 3:1})
set_d = set("abcd")
set_e = set(["a", "b", "cd"])
print set_a, set_b, set_c
print set_d, set_e
# set([1, 2, 3]) set([1, 2, 3]) set([1, 2, 3])
# set(['a', 'b', 'c', 'd']) set(['a', 'b', 'cd']) # set的交、并、差运算、对称差集
# 这些操作返回一个新的集合,set_a,set_b不发生改变
set_a = set([1, 2, 3])
set_b = set([2, 3, 4])
print set_a.intersection(set_b)
# set([2, 3])
print set_a.union(set_b)
# set([1, 2, 3, 4])
print set_a.difference(set_b)
# set([1])
print set_a.symmetric_difference(set_b)
# set([1, 4]) # 这些对应操作没有返回值,并且直接改变set_a,set_b不变化
set_a.intersection_update(set_b)
set_a.update(set_b) # 添加多个元素,就是union含义
set_a.difference_update(set_b)
set_a.symmetric_difference_update(set_b) # set 其他的操作符
s = set([1, 2, 3, 4])
s.add(5) # 添加单个元素
s.add(4) # 重复元素没有效果
s.remove(4) # 移除集合中的元素,如果元素不存在会报错
s.discard(8) # 也是移除集合中的元素,但是对于不存在元素不影响
s.pop() # 弹出集合中的任意一个元素,如果集合为空执行该操作报错 set([2, 9, 7, 1].issubset(set([1, 7])) # 判断是否是子集,返回True或者False
set([2, 9, 7, 1]).issuperset(set([1, 7])) # 判断是否是父集,返回True或者Falseset因为是无序集合,所以不支持index索引和slice([ ])切片的操作,可以用element in set来判断集合是否存在该元素。或者for 循环用iterable遍历。
set和list是可变类型,下面的a和b都是指向同一个空间位置。
a = set([1, 2, 3])
b = a
print a
# set([1, 2, 3])
b.add(4)
print a
# set([1, 2, 3, 4])
Python知识介绍完,上游戏图
回顾上周,完成了一个飞船,一个陨石,一个子弹。
基本的绘制、更新以及大部分的方法都已经实现,这一周主要是实现多个陨石以及子弹的连续发射,还有加上飞船三者之间碰撞的关系处理。
对于游戏来说,掌握关键的一帧,核心就在draw的绘制中:
· 更新时间,绘制背景元素
· 绘制和更新my_ship
· 如果游戏开始
· 绘制更新rock_group
· 绘制更新missile_group
· 绘制更新explosion_group
· 如果 rock_group和my_ship碰撞,lives –= 1
· 如果 lives<=0,init_game()
· score 加上发生rock_group与missile_group碰撞的数量(biu~~~~)
· 游戏没开始就换个splah提示
· 绘制lives text
· 绘制score text
# draw handler
def draw(canvas):
global time, lives, rock_group, missile_group, my_ship, score, started # animiate background
time += 1
wtime = (time / 4) % WIDTH
center = debris_info.get_center()
size = debris_info.get_size()
canvas.draw_image(nebula_image, nebula_info.get_center(), nebula_info.get_size(), [WIDTH / 2, HEIGHT / 2], [WIDTH, HEIGHT])
canvas.draw_image(debris_image, center, size, (wtime - WIDTH / 2, HEIGHT / 2), (WIDTH, HEIGHT))
canvas.draw_image(debris_image, center, size, (wtime + WIDTH / 2, HEIGHT / 2), (WIDTH, HEIGHT)) # draw and update ship
my_ship.draw(canvas)
my_ship.update() if started: # draw and update rock_group
process_sprite_group(rock_group, canvas) # draw and update missile_group
process_sprite_group(missile_group, canvas) # draw and update explosioin_group
process_sprite_group(explosion_group, canvas) # ship - rock_group collide and update the lives
if group_collide(rock_group, my_ship):
lives -= 1
# game over
if lives <= 0:
init_game()
message_label.set_text('Click to start!')
# update score
score += group_group_collide(rock_group, missile_group) * 10 else:
canvas.draw_image(splash_image, splash_info.get_center(), splash_info.get_size(), (WIDTH / 2, HEIGHT / 2), splash_info.get_size()) # draw lives
canvas.draw_text("Lives", [WIDTH / 12, HEIGHT / 12], 30, "White")
canvas.draw_text(str(lives), [WIDTH / 12, HEIGHT / 12 + 40], 30, "White") # draw score
canvas.draw_text("Score", [10 * WIDTH / 12, HEIGHT/12], 30, "White")
canvas.draw_text(str(score), [10 * WIDTH /12, HEIGHT/12 + 40], 30, "White")
my_ship的绘制更新上周已经完成,上周主要处理一个陨石和子弹的情形,这周完成多个,奥秘就在process_sprite_group()中。
def process_sprite_group(sprite_group, canvas):
remove_set = set([])
for sprite in sprite_group:
sprite.draw(canvas)
if sprite.update():
remove_set.add(sprite)
sprite_group.difference_update(remove_set)process_sprite_group完成对group中每一个对象的绘制和更新,sprite.update()的if判断主要是针对子弹,子弹发生是存在距离,这个距离通过age时间来衡量,当sprite.lifespan超过了age,那么update就返回True,我们就要把这些过了保质期的子弹从他的group中移走,而默认rock陨石的lifespan保质期是inf,永不过期,除非被飞机打掉了。这样通过process_sprite_group就可以维护rock_group,missile_group,explosion_group.
那么接下来重要的问题来了?挖掘机….处理元素之间的碰撞关系。这里碰撞主要存在两种,飞机与陨石之间碰撞,陨石和子弹之间碰撞。
def group_collide(group, other_object):
is_collide = False
remove_set = set([])
for obj in group:
if obj.collide(other_object):
is_collide = True
remove_set.add(obj)
# create new explosion
pos = [other_object.pos[0], other_object.pos[1]]
new_explosion = Sprite(pos, [0, 0], other_object.angle, 0, explosion_image, explosion_info, explosion_sound)
explosion_group.add(new_explosion) group.difference_update(remove_set)
return is_collide def group_group_collide(group, other_group):
num_collide = 0
for obj in list(group):
if group_collide(other_group, obj):
group.discard(obj)
num_collide += 1
return num_collide通过上面两个Help Function,第一个可以用来检测rock_group和my_ship,需要实现一个sprite的collide方法,用距离和半径和的关系判断是否碰撞,然后再group_collide函数中只要遍历rock_group,调用collide方法判断是否和my_ship相撞。相撞的元素从group中移走,为了实现explosion的效果,在这里向explosion_group添加以other_project坐标属性的新元素。
第二个,主要用来监测rock_group和missile_group的碰撞关系,遍历rock_group然后在调用group_collide方法,判断单个元素和group之间碰撞(复用大法好)。
碰撞检测后,记得更新score 和 lives的值。
游戏的核心也就差不多了。剩下的就没什么了。started变量控制一下游戏开始状态,基本的Tile的图像绘制,加个偏移就好。再有就是一些加速的参数需要自己手工调整,关乎你游戏的可玩性。忘记一点,随机生成rock的时候,加一个判断当坐标离自己飞船在一定范围之外,才能生成,不然莫宁奇妙的躺枪。
一个陨石10分,无聊的加了个button,500分换一条命。
下面就贴完整代码:
# program template for Spaceship
import simplegui
import math
import random # globals for user interface
WIDTH = 800
HEIGHT = 600
score = 0
lives = 3
time = 0.5 class ImageInfo:
def __init__(self, center, size, radius = 0, lifespan = None, animated = False):
self.center = center
self.size = size
self.radius = radius
if lifespan:
self.lifespan = lifespan
else:
self.lifespan = float('inf')
self.animated = animated def get_center(self):
return self.center def get_size(self):
return self.size def get_radius(self):
return self.radius def get_lifespan(self):
return self.lifespan def get_animated(self):
return self.animated # art assets created by Kim Lathrop, may be freely re-used in non-commercial projects, please credit Kim # debris images - debris1_brown.png, debris2_brown.png, debris3_brown.png, debris4_brown.png
# debris1_blue.png, debris2_blue.png, debris3_blue.png, debris4_blue.png, debris_blend.png
debris_info = ImageInfo([320, 240], [640, 480])
debris_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/debris2_blue.png") # nebula images - nebula_brown.png, nebula_blue.png
nebula_info = ImageInfo([400, 300], [800, 600])
nebula_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/nebula_blue.f2014.png") # splash image
splash_info = ImageInfo([200, 150], [400, 300])
splash_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/splash.png") # ship image
ship_info = ImageInfo([45, 45], [90, 90], 35)
ship_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/double_ship.png") # missile image - shot1.png, shot2.png, shot3.png
missile_info = ImageInfo([5,5], [10, 10], 3, 50)
missile_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/shot2.png") # asteroid images - asteroid_blue.png, asteroid_brown.png, asteroid_blend.png
asteroid_info = ImageInfo([45, 45], [90, 90], 40)
asteroid_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/asteroid_blue.png") # animated explosion - explosion_orange.png, explosion_blue.png, explosion_blue2.png, explosion_alpha.png
explosion_info = ImageInfo([64, 64], [128, 128], 17, 24, True)
explosion_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/explosion_alpha.png") # sound assets purchased from sounddogs.com, please do not redistribute
soundtrack = simplegui.load_sound("http://commondatastorage.googleapis.com/codeskulptor-assets/sounddogs/soundtrack.mp3")
missile_sound = simplegui.load_sound("http://commondatastorage.googleapis.com/codeskulptor-assets/sounddogs/missile.mp3")
missile_sound.set_volume(.5)
ship_thrust_sound = simplegui.load_sound("http://commondatastorage.googleapis.com/codeskulptor-assets/sounddogs/thrust.mp3")
explosion_sound = simplegui.load_sound("http://commondatastorage.googleapis.com/codeskulptor-assets/sounddogs/explosion.mp3") # helper functions to handle transformations
def angle_to_vector(ang):
return [math.cos(ang), math.sin(ang)] def dist(p,q):
return math.sqrt((p[0] - q[0]) ** 2+(p[1] - q[1]) ** 2) # Ship class
class Ship:
def __init__(self, pos, vel, angle, image, info):
self.pos = [pos[0],pos[1]]
self.vel = [vel[0],vel[1]]
self.thrust = False
self.angle = angle
self.angle_vel = 0
self.image = image
self.image_center = info.get_center()
self.image_size = info.get_size()
self.radius = info.get_radius() def draw(self,canvas):
if self.thrust:
center = (self.image_center[0]+self.image_size[0], self.image_center[1])
canvas.draw_image(self.image, center, self.image_size, self.pos, self.image_size, self.angle)
else:
canvas.draw_image(self.image, self.image_center, self.image_size, self.pos, self.image_size, self.angle) def update(self):
self.angle += self.angle_vel
self.pos[0] = (self.pos[0] + self.vel[0]) % WIDTH
self.pos[1] = (self.pos[1] + self.vel[1]) % HEIGHT
c = 0.05
self.vel[0] *= (1 - c)
self.vel[1] *= (1 - c)
forward = angle_to_vector(self.angle)
if self.thrust:
self.vel[0] += forward[0] * 0.8
self.vel[1] += forward[1] * 0.8 def change_angle_vel(self, ori, key_state):
if ((ori == "right" and key_state == "keyup") or
(ori == "left" and key_state == "keydown")):
self.angle_vel -= 0.1
elif ((ori == "right" and key_state == "keydown") or
(ori == "left" and key_state == "keyup")):
self.angle_vel += 0.1 def set_thruster(self, thruster_state):
self.thrust = thruster_state
if self.thrust:
ship_thrust_sound.rewind()
ship_thrust_sound.play()
else:
ship_thrust_sound.rewind() def shoot(self):
global missile_group
offset = self.image_size[0] / 2.0
forward = angle_to_vector(self.angle)
pos = [self.pos[0] + offset * forward[0], self.pos[1] + offset * forward[1]]
vel = [self.vel[0] + 6 * forward[0], self.vel[1] + 6 * forward[1]]
ang = 0
ang_vel = 0
missile_group.add(Sprite(pos, vel, ang, ang_vel, missile_image, missile_info, missile_sound)) def get_position(self):
return self.pos def get_radius(self):
return self.radius # Sprite class
class Sprite:
def __init__(self, pos, vel, ang, ang_vel, image, info, sound = None):
self.pos = [pos[0],pos[1]]
self.vel = [vel[0],vel[1]]
self.angle = ang
self.angle_vel = ang_vel
self.image = image
self.image_center = info.get_center()
self.image_size = info.get_size()
self.radius = info.get_radius()
self.lifespan = info.get_lifespan()
self.animated = info.get_animated()
self.age = 0
if sound:
sound.rewind()
sound.play() def draw(self, canvas):
if self.animated:
center = (self.image_center[0] + self.age * self.image_size[0], self.image_center[1])
canvas.draw_image(self.image, center, self.image_size, self.pos, self.image_size, self.angle)
else:
canvas.draw_image(self.image, self.image_center, self.image_size, self.pos, self.image_size, self.angle) def update(self):
if started:
self.angle += self.angle_vel
self.pos[0] = (self.pos[0] + self.vel[0]) % WIDTH
self.pos[1] = (self.pos[1] + self.vel[1]) % HEIGHT self.age += 1
if self.age >= self.lifespan:
return True
else:
return False def get_position(self):
return self.pos def get_radius(self):
return self.radius def collide(self, other_object):
dis = self.get_radius() + other_object.get_radius()
if dis > dist(self.get_position(), other_object.get_position()):
return True
else:
return False # Help Function to deal collision
def group_collide(group, other_object):
is_collide = False
remove_set = set([])
for obj in group:
if obj.collide(other_object):
is_collide = True
remove_set.add(obj)
# create new explosion
pos = [other_object.pos[0], other_object.pos[1]]
new_explosion = Sprite(pos, [0, 0], other_object.angle, 0, explosion_image, explosion_info, explosion_sound)
explosion_group.add(new_explosion) group.difference_update(remove_set)
return is_collide def group_group_collide(group, other_group):
num_collide = 0
for obj in list(group):
if group_collide(other_group, obj):
group.discard(obj)
num_collide += 1
return num_collide def process_sprite_group(sprite_group, canvas):
remove_set = set([])
for sprite in sprite_group:
sprite.draw(canvas)
if sprite.update():
remove_set.add(sprite)
sprite_group.difference_update(remove_set) # draw handler
def draw(canvas):
global time, lives, rock_group, missile_group, my_ship, score, started # animiate background
time += 1
wtime = (time / 4) % WIDTH
center = debris_info.get_center()
size = debris_info.get_size()
canvas.draw_image(nebula_image, nebula_info.get_center(), nebula_info.get_size(), [WIDTH / 2, HEIGHT / 2], [WIDTH, HEIGHT])
canvas.draw_image(debris_image, center, size, (wtime - WIDTH / 2, HEIGHT / 2), (WIDTH, HEIGHT))
canvas.draw_image(debris_image, center, size, (wtime + WIDTH / 2, HEIGHT / 2), (WIDTH, HEIGHT)) # draw and update ship
my_ship.draw(canvas)
my_ship.update() if started: # draw and update rock_group
process_sprite_group(rock_group, canvas) # draw and update missile_group
process_sprite_group(missile_group, canvas) # draw and update explosioin_group
process_sprite_group(explosion_group, canvas) # ship - rock_group collide and update the lives
if group_collide(rock_group, my_ship):
lives -= 1
# game over
if lives <= 0:
init_game()
message_label.set_text('Click to start!')
# update score
score += group_group_collide(rock_group, missile_group) * 10 else:
canvas.draw_image(splash_image, splash_info.get_center(), splash_info.get_size(), (WIDTH / 2, HEIGHT / 2), splash_info.get_size()) # draw lives
canvas.draw_text("Lives", [WIDTH / 12, HEIGHT / 12], 30, "White")
canvas.draw_text(str(lives), [WIDTH / 12, HEIGHT / 12 + 40], 30, "White") # draw score
canvas.draw_text("Score", [10 * WIDTH / 12, HEIGHT/12], 30, "White")
canvas.draw_text(str(score), [10 * WIDTH /12, HEIGHT/12 + 40], 30, "White") # timer handler that spawns a rock
def rock_spawner():
global rock_group
if started and len(rock_group) < 12:
pos = [random.randint(0, WIDTH-1), random.randint(0, HEIGHT-1)]
# dist great than 20 can spawn a new rock
if dist(pos, my_ship.get_position()) > 150:
vel = [random.randrange(1, 3, 1)*random.choice([1, -1]), random.randrange(1, 3, 1)*random.choice([1, -1])]
ang = 0
ang_vel = random.randrange(5, 10, 1) / 100.0 * random.choice([1, -1])
new_rock = Sprite(pos, vel, ang, ang_vel, asteroid_image, asteroid_info)
rock_group.add(new_rock) # key_up handler
def key_up(key):
if started:
if simplegui.KEY_MAP['left'] == key:
my_ship.change_angle_vel("left", "keyup")
elif simplegui.KEY_MAP['right'] == key:
my_ship.change_angle_vel("right", "keyup")
elif simplegui.KEY_MAP['up'] == key:
my_ship.set_thruster(False) # key_down handler
def key_down(key):
if started:
if simplegui.KEY_MAP['left'] == key:
my_ship.change_angle_vel("left", "keydown")
elif simplegui.KEY_MAP['right'] == key:
my_ship.change_angle_vel("right", "keydown")
elif simplegui.KEY_MAP['up'] == key:
my_ship.set_thruster(True)
elif simplegui.KEY_MAP['space'] == key:
my_ship.shoot() # mouse_click handler
def mouse_click(position):
global started
if position[0] < WIDTH and position[1] < HEIGHT and not started :
started = True
soundtrack.rewind()
soundtrack.play()
message_label.set_text('Welcome, enjoy!') # puchase button handler:
def purchase_button():
global score, lives, message_label
if started:
if score >= 500:
score -= 500
lives += 1
message_label.set_text("Purchase successfully.")
else:
message_label.set_text("Scores are not enough.")
else:
message_label.set_text("Game hasn't started yet.") # exit game button
def exit_button():
soundtrack.rewind()
ship_thrust_sound.rewind()
missile_sound.rewind()
explosion_sound.rewind()
frame.stop() # init the game state
def init_game():
global my_ship, rock_group, missile_group, explosion_group, started, score, lives
# initialize ship and two sprites
my_ship = Ship([WIDTH / 2, HEIGHT / 3], [0, 0], 1.5*math.pi, ship_image, ship_info)
rock_group = set([])
missile_group = set([])
explosion_group = set([])
score, lives = 0, 3
started = False
soundtrack.rewind()
ship_thrust_sound.rewind()
missile_sound.rewind()
explosion_sound.rewind() # initialize frame
frame = simplegui.create_frame("Asteroids", WIDTH, HEIGHT) # init the game
init_game() # register handlers
frame.set_draw_handler(draw)
frame.set_keydown_handler(key_down)
frame.set_keyup_handler(key_up)
frame.set_mouseclick_handler(mouse_click)
frame.add_button('500 Scores for a life', purchase_button, 200)
frame.add_button('Quit', exit_button, 200)
message_label = frame.add_label('Click to start!')
author_label = frame.add_label('Tiny656')
contact_label = frame.add_label('236798656@qq.com')
timer = simplegui.create_timer(1000.0, rock_spawner) # get things rolling
timer.start()
frame.start()
坐等最后的Peer Evaluation,这么课应该就结束了,感谢Rice大学这些兢兢业业对于教学富有激情和创意的老师,能让我有幸聆听到这么有意思的课程,收获满满,感谢Coursera这么棒棒的平台,拉近了每个人与知识的距离,对于充满好奇心的我,一比无价的财富。现在的环境是,永远不缺知识以及还有这么多优秀的知识分享者,缺少一颗渴望学习的心,不管做什么,耐心和毅力总能感觉到自己不断成长的步伐,学习的道路上,永远不应该放下脚步,引用Jobs的话,stay hungry, stay foolish, 求知若饥求知若愚。
接下来开始认真跟Princeton的算法II,妈蛋的,第二周都放出来了,第一周的视频还一点没看,得抓紧。还有老板的活要干,还有英语要复习,真是分神乏力。咬咬牙,坚持下来。12月开始刷题复习,明年找工作。