Mini projects #8–RiceRocks
课程全名: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月开始刷题复习,明年找工作。
作者:
Tiny656
出处:http://www.cnblogs.com/tiny656
欢迎交流讨论. 236798656 [at] qq.com