首页 > 代码库 > Mini projects #8–RiceRocks

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类似,用来维护一组不重复的无序元素。

# 定义一个空的setset_empty = set() # or set([])print set_empty# set([])# 定义setset_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或者Falseset([2, 9, 7, 1]).issuperset(set([1, 7])) # 判断是否是父集,返回True或者False

set因为是无序集合,所以不支持index索引和slice([ ])切片的操作,可以用element in set来判断集合是否存在该元素。或者for 循环用iterable遍历。

set和list是可变类型,下面的a和b都是指向同一个空间位置。

a = set([1, 2, 3])b = aprint a# set([1, 2, 3])b.add(4)print a# set([1, 2, 3, 4])

 

Python知识介绍完,上游戏图

image

 

回顾上周,完成了一个飞船,一个陨石,一个子弹。

基本的绘制、更新以及大部分的方法都已经实现,这一周主要是实现多个陨石以及子弹的连续发射,还有加上飞船三者之间碰撞的关系处理。

对于游戏来说,掌握关键的一帧,核心就在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 handlerdef 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_collidedef 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 Spaceshipimport simpleguiimport mathimport random# globals for user interfaceWIDTH = 800HEIGHT = 600score = 0lives = 3time = 0.5class 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.pngdebris_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.pngnebula_info = ImageInfo([400, 300], [800, 600])nebula_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/nebula_blue.f2014.png")# splash imagesplash_info = ImageInfo([200, 150], [400, 300])splash_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/splash.png")# ship imageship_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.pngmissile_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.pngasteroid_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.pngexplosion_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 redistributesoundtrack = 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 transformationsdef 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 classclass 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 classclass 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 collisiondef 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_collidedef 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_collidedef 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 handlerdef 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 rockdef 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 handlerdef 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 handlerdef 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 buttondef exit_button():    soundtrack.rewind()    ship_thrust_sound.rewind()    missile_sound.rewind()    explosion_sound.rewind()    frame.stop()# init the game statedef 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 frameframe = simplegui.create_frame("Asteroids", WIDTH, HEIGHT)# init the gameinit_game()# register handlersframe.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 rollingtimer.start()frame.start()

 

坐等最后的Peer Evaluation,这么课应该就结束了,感谢Rice大学这些兢兢业业对于教学富有激情和创意的老师,能让我有幸聆听到这么有意思的课程,收获满满,感谢Coursera这么棒棒的平台,拉近了每个人与知识的距离,对于充满好奇心的我,一比无价的财富。现在的环境是,永远不缺知识以及还有这么多优秀的知识分享者,缺少一颗渴望学习的心,不管做什么,耐心和毅力总能感觉到自己不断成长的步伐,学习的道路上,永远不应该放下脚步,引用Jobs的话,stay hungry, stay foolish, 求知若饥求知若愚。

接下来开始认真跟Princeton的算法II,妈蛋的,第二周都放出来了,第一周的视频还一点没看,得抓紧。还有老板的活要干,还有英语要复习,真是分神乏力。咬咬牙,坚持下来。12月开始刷题复习,明年找工作。

Mini projects #8–RiceRocks