• 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或者False

    set因为是无序集合,所以不支持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知识介绍完,上游戏图

    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 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月开始刷题复习,明年找工作。

  • 相关阅读:
    Python 如何计算当前时间减少或增加一个月
    删除 win8.1中的网络1,网络2,宽带连接1,宽带连接2等网络记录
    Office2003/2010等集成SP的简单方法
    win8.1点击“更改电脑设置”无反应(闪退)
    右键菜单添加带图标的Notepad++
    word2010无法打开文件时的一点对策
    在win7/8/10鼠标右键添加“管理员取得所有权”
    VisualSVNServer 无法启动 could not log pid to file
    半年来经销商云平台工作总结-后端
    半年来经销商云平台工作总结-前端
  • 原文地址:https://www.cnblogs.com/tiny656/p/4086168.html
Copyright © 2020-2023  润新知