1.视频效果演示

python自制黄金矿工,细节拉满沉浸式体验,看了你也会

2.开发准备的工具

python3.8, pygame库(python3.5以上的版本应该都可以)

图片处理工具,美图秀秀

截图工具,电脑自带的

自动抠图网页:https://koutu.gaoding.com/m

mp4格式文件转游戏音频:https://www.aconvert.com/cn/audio/mp4-to-wav/#google_vignette

声音录制工具,oCam

3.开发和设计思路

素材来源:主要是在4399的黄金矿工游戏,录制游戏音频,和截图游戏里面的元素

游戏设计了12个关卡,

3.1游戏中的元素

游戏矿石7种:大黄金,小黄金,中等黄金,钻石,小石头,大石头,盲盒

大黄金价值:500

小黄金价值:75

中等黄金价值:200

钻石价值:600

小石头价值:20

大石头价值:60

盲盒:  -300 --- 700  也就是有可能,抓的盲盒是负的

钻石会随着等级的升级而变得更贵,

7种矿石在每局游戏种会随机出现。并不会每次全部出现。

游戏道具6种:小黄金升值道具,钻石升值道具,钻石数量增加道具,时间延长道具,幸运四叶草,强壮水,石头收藏书

在商城里面每次只会提供三种道具,这是随机的

游戏道具每次的价格是随机的所以要根据你的金钱数量,调整游戏策略

小黄金道具:如果购买了小黄金的价格会变成125 + 25 * 游戏关卡,小黄金的价格

钻石升值道具:使用了钻石变成600 + 300 增值300  300 + 75 * Game_config.level

钻石数量增加道具:如果在商店购买了,钻石的数量会在之前的基础上,生成钻石的随机值,最大的可能值会+3

时间延长道具:使用之后游戏时间会变成80秒,正常一局游戏60s

幸运四叶草: 幸运四叶草,在商店买了的化,抓取到了神秘礼包盲盒,金额会随机的变多

     500 -- 1000

    否则是

     -300 --- 700

强壮水:抓取物品的速度会变的更快,speed +2

石头收藏书: 石头增值道具,如果买了大石头的价格会*4

1到12关的目标金额: [1000, 2200, 3600, 5400, 7500, 10000, 12500, 17000, 21500, 26000, 30000, 45000]

3.2音频素材

音频素材6段:抓到大黄金的声音,抓到石头的声音,绳子变长的声音,金钱变多的声音,绳子摆动的声音,目标分数的声音;

3.2游戏界面

游戏界面四个:开始界面,游戏界面,商城界面,失败或者结束界面

3.3关于如何让矿工动起来

关于如何让矿工动起来: 使用不同的图片,让pygame在不同时间播放不同的图片。

我最喜欢这个游戏的部分就是,矿工满头大汗,抓矿石的样子,哈哈哈。

还有就是商城道具和矿石的随机性

3.4如何让绳子和钩子动起来

如何让钩子和绳子动起来。

钩子的摆动幅度是-70 到 70 度,每次变化的幅度是3度

这里使用了三角函数,sin cos 来实现

总共有9张钩子的图片,随着角度的变化,显示不同的图片

3.5关于每个关卡矿石的生成策略

#每个关卡游戏界面物品的生成

 钻石的基础数量是0

如果在商店买了钻石水 3

如果等级能被4整除

 钻石的数量 no_diamond += 2 +int(level/3)

 如果等级大于6

钻石的数量 no_diamond += 2

 如果等级大于等于8

钻石的数量 no_diamond += 4

 如果等级大于等于12

钻石的数量 no_diamond += 10

 神秘包裹盲盒的数量

如果等级能被2整除 no_random += 3 +int(level/2)

否则 no_random = 1

 大黄金的数量

如果等级大于等于3no_big = 1 no_big += level//2

否则大黄金数量等于0

 如果等级大于等于6 no_big += 2

 如果等级大于等于8 no_big += 3

如果等级大于等于12 no_big = 6

base 一些物品的最小基础值

如果等级大于等于6 base = 1 否则 base = 0

如果等级大于等于8 base = 2

 如果等级大于等于12 base = 5

 如果等级大于等于8 小石头的数量[0,2]

 如果等级大于等于8 小黄金的数量[0,2或者3]

如果等级小于8 小石头的数量 [5,7]

如果等级小于8 小黄金的数量 5 + level // 2, 12 + level // 2

中等黄金的数量

0, random.randint(5+level//2, 8+level//2)

大石头的数量

 0, random.randint(1 + level // 4 + base, 3 + level // 4 + base)

 大黄金的数量

 0, random.randint(base, no_big)

 钻石的数量

0, random.randint(base, no_diamond)

神秘包裹的数量

0, random.randint(base, no_random)

游戏的fps

ticks_per_second = 30 表示是1000/30 的刷新频率

如果生成的物品模型发生了重叠,那么要重新生成

3.6代码文件

代码文件:gamebox.py  对pygame库的二次封装

Gold_mine.py 游戏逻辑主要代码

4.代码部分

gold_miner.py文件

import time

import  pygame
from enum import Enum, unique
import gamebox
import random
import math

@unique    #此装饰器可以帮助我们检查保证没有重复值
class Screen(Enum):
    screen_begin        = 0
    screen_game         = 1
    screen_shop         = 2
    screen_target_score = 3
    screen_failed       = 4


@unique    #此装饰器可以帮助我们检查保证没有重复值
class Character(Enum):
    character_up                 = 0
    character_down               = 1


class Character_Getthing(Enum):
    character_up_getthin = 0
    character_down_getthin = 1


class Game_Background(Enum):
    background_begin = 0
    background_game_heard = 1
    background_game_body  = 2
    background_shop = 3
    background_target_score = 4
    background_failed = 5


class Audio(Enum):
    audio_money_increase = 0
    audio_target_score   = 1
    audio_rope_swing     = 2
    audio_go_long        = 3
    audio_get_big_gold   = 4
    audio_get_stone      = 5
#矿石
@unique
class Mineral(Enum):
    gold_small  = 0
    gold_middle = 1
    gold_big    = 2
    stone_small = 3
    stone_big   = 4
    Diamond     = 5
    random_big  = 6

#钩子的每一帧图片

hook_dict = {}
hook_dict[-70] = "hook/hook_70.png"
hook_dict[-43] = "hook/hook_43.png"
hook_dict[-31] = "hook/hook_31.png"
hook_dict[-16] = "hook/hook_16.png"
hook_dict[1] = "hook/hook_1.png"
hook_dict[14] = "hook/hook14.png"
hook_dict[32] = "hook/hook32.png"
hook_dict[44] = "hook/hook44.png"
hook_dict[68] = "hook/hook68.png"




#游戏配置类
class Game_config():
    speed = 5
    # 游戏关卡,从第一关开始
    level = 1
    #游戏金钱
    money = 0
    #钩子摆动的角度 -70 ---  70
    radins = -70
    #摆动的角度每次偏移的值
    radins_offset = 3
    radins_min = -70
    radins_max = 70
    #绳子的初始长度距离
    chain_distance = 30
    #绳子的变化速度 抓到不同的重量 速度不一样
    weight_item_caught = speed +5
    #钩子的变化速度控制,抓到不同物体速度变化不一样
    pict_index = 0
    #控制一局游戏的时间 和刷新频率有关系 当前1s刷新15次,计数器60s刷新900次
    counter = 900
    popped_up_word_counterdown = 16
    #大黄金的价格和标志
    gold_large_value = 500
    gold_small_value = 75
    gold_middle_value = 200
    dimaond_value = 600
    small_rock_value = 20
    big_rock_value  =  60
    #抓到了神秘背包,那么绳子移动速度是随机的(-4,+5)
    #价值也是随机的
    # 每局游戏的目标分数
    money_goal = [1000, 2200, 3600, 5400, 7500, 10000, 12500, 17000, 21500, 26000, 30000, 45000]
    #矿石生成的随机区域的范围控制
    mineral_race_min_x = 50
    mineral_race_max_x =  900
    mineral_race_min_y =  240
    mineral_race_max_y =  690

    #按钮的范围绘制
    #开始按钮的坐标范围
    button_start_min_x = 38
    button_start_max_x = 400
    button_start_min_y = 18
    button_start_max_y = 370

    #音频文件
    #游戏开始和目标分数的音频
    sound_start ="sound/audio_target_score.wav"
    #绳子摆动的音频
    sound__rope_swing = "sound/audio_rope_swing.wav"
    #金钱增加的声音
    sound__money_increase = "sound/audio_money_increase.wav"
    #绳子变长的声音
    sound_go_long = "sound/audio_go_long.wav"
    #抓到石头的声音
    sound_get_stone = "sound/audio_get_stone.wav"
    #抓到大黄金的声音
    sound_get_big_gold = "sound/audio_get_big_gold.wav"
    #商店的三个格子每次只能点击一次
    store_index = False
    store_index2 = False
    store_index3 = False
    #生成物品的算法如果发生了碰撞最多尝试10次
    try_times = 10

#商店
class Store():
    # 如果购买了小黄金的价格会变成125 + 25 * level
    item_gold_modifer = False
    # 如果购买了,钻石的价值会增加300
    item_polisher = False
    # 如果在商店购买了,钻石的数量会在之前的基础上,生成钻石的随机值,最大的可能值会+3
    item_lamp = False
    # 时间延长器如果在商店买了计数增加到1200
    item_time = False
    #幸运四叶草,在商店买了的化,抓取到了神秘礼包,金额会随机的变多
    # 500 -- 1000
    # 否则是
    # -300 --- 700
    item_luck = False
    #如果购买了会变得强壮速度+2
    item_strong = False
    # 石头增值道具,如果买了大石头的价格会*4
    item_rocks = False

#商品的枚举类型
@unique
class Store_index(Enum):
    index_gold_modifer = 0
    index_polisher = 1
    index_lamp = 2
    index_time = 3
    index_luck = 4
    index_strong = 5
    index_rocks = 6


dict_goods_picture = {}
dict_goods_picture[Store_index.index_gold_modifer.value] = "goods/gold_modifer.png"
dict_goods_picture[Store_index.index_polisher.value] = "goods/polisher.png"
dict_goods_picture[Store_index.index_lamp.value] = "goods/lamp.png"
dict_goods_picture[Store_index.index_time.value] = "goods/time.png"
dict_goods_picture[Store_index.index_luck.value] = "goods/luck.png"
dict_goods_picture[Store_index.index_rocks.value] = "goods/rocks.png"
dict_goods_picture[Store_index.index_strong.value] = "goods/strong.png"
dict_goods_text = {}

dict_goods_text[Store_index.index_gold_modifer.value] = "小黄金会变得更值钱"
dict_goods_text[Store_index.index_polisher.value] = "钻石会变得更值钱"
dict_goods_text[Store_index.index_lamp.value] = "钻石会变得更多"
dict_goods_text[Store_index.index_time.value] = "游戏时间会变得更长"
dict_goods_text[Store_index.index_luck.value] = "神秘礼包的金额会变得更多"
dict_goods_text[Store_index.index_rocks.value] = "大石头的价格会增加4倍"
dict_goods_text[Store_index.index_strong.value] = "矿工会变更强壮,加快"


#矿石图片存储列表
picture_mineral   = ["picture/gold_small.png","picture/gold_middle.png","picture/gold_big.png","picture/rock_small.png","picture/rock_big.png","picture/dimaond.png","picture/mystery_bag.png"]
#平常状态的角色
picture_list_character = ["picture/character_up.png","picture/character_down.png"]
#抓取到东西的时候的角色图片
picture_list_character_getthing = ["picture/character_up_getthin.png","picture/character_down_getthin.png"]
#商品的图片
picture_list_goods = ["picture/character_up_getthin.png"]
#音频文件
sound_list  = ["sound/audio_money_increase.png","sound/audio_target_score.png","sound/audio_rope_swing.png","picture/audio_go_long.png","picture/audio_get_big_gold.png","picture/audio_get_stone7.png"]
picture_list_screen = ["picture/screen_begin.png","picture/screen_game_head.png","picture/screen_game_body.png","picture/screen_shop.png","picture/screen_target_score.png","picture/screen_failed.png"]




#全局变量
#界面index
#0开始界面
#1游戏界面
#2商城界面
#3目标分数界面
#4失败界面
screen = 0
#游戏界面的长宽
camera=gamebox.Camera(1000, 700)
# 在左上角绘制文字
pygame.display.set_caption('制作人:慕白白ls')

#Item value
#抓到的东西的价格
value_caught = 0
#人物角色的控制每一帧显示的图片
character_index =0

#bool 游戏中元素的状态值
#钩子的状态
chain_thrown_out= False
#钩子的方向状态 右-> 左  or 左->右
direction_left_to_right=True
#钩子是否被仍出去了
chain_thrown_out= False
#绳子是伸长状态还是收缩状态
chain_thrown_out_away = True
#钩子是否抓取到了东西
chain_thrown_out_catchsomething = False


#钩子对象
chainhead = gamebox.from_image(200, 200, hook_dict[-70])
#人物对象
character = gamebox.from_image(507, 41, picture_list_character[0])

character_getthing = gamebox.from_image(507, 41, picture_list_character_getthing[character_index])

#显示在游戏界面的物品
gold_middle_object = gamebox.from_image(400,400,picture_mineral[Mineral.gold_middle.value])
gold_small_object = gamebox.from_image(500,500,picture_mineral[Mineral.gold_small.value])
gold_large_object = gamebox.from_image(700,500,picture_mineral[Mineral.gold_big.value])
gold_dimaond_object = gamebox.from_image(700, 500, picture_mineral[Mineral.Diamond.value])
gold_small_rock_object = gamebox.from_image(400, 600, picture_mineral[Mineral.stone_small.value])
gold_big_rock_object = gamebox.from_image(600, 600, picture_mineral[Mineral.stone_big.value])
gold_random_big_object = gamebox.from_image(200, 300,picture_mineral[Mineral.random_big.value])

hard_object = gamebox.from_image(580, 20,"picture/strong.png")
#list
gold_middle_list = []
gold_small_list = []
gold_large_list = []
gold_dimaond_list = []
gold_small_rock_list = []
gold_big_rock_list = []
gold_random_big_list = []

#商品存储列表
shop_list = []
shop_price = []


def Set_Store_value(value):
    if(value == Store_index.index_gold_modifer.value):
        Store.item_gold_modifer = True
    if (value == Store_index.index_polisher.value):
        Store.item_polisher = True
    if (value == Store_index.index_lamp.value):
        Store.item_lamp = True
    if (value == Store_index.index_time.value):
        Store.item_time = True
    if (value == Store_index.index_luck.value):
        Store.item_luck = True
    if (value == Store_index.index_strong.value):
        Store.item_strong = True
    if (value == Store_index.index_rocks.value):
        Store.item_rocks = True
    if Store.item_strong:
        Game_config.speed += 2





#回调函数,循环接收键盘和鼠标的消息
#钩子的变化范围是从-70 的角度 变化到70
def Callback_Loop(keys):
    global screen , camera ,character_index,character,value_caught
    global chain_thrown_out_catchsomething,chain_thrown_out,direction_left_to_right
    global chain_thrown_out,chain_thrown_out_away,shop_price,shop_price
    global shop_list
    camera.clear('grey')
    if screen == Screen.screen_begin.value:
        #设置显示的图片大小长,宽
        #screen_surface = gamebox.from_image(500, 350, picture_list_screen[Game_Background.background_begin.value])
        screen_surface = gamebox.from_image(500, 350, "picture/begin.png")
        screen_surface.scale_by(0.9)
        camera.draw(screen_surface)
        camera.draw(gamebox.from_text(200, 430, "制作人:慕白白ls", "SimHei", 30, "yellow"))
        # 文字信息
        camera.draw(gamebox.from_text(200, 500, "点击开始按钮开始游戏", "SimHei", 30, "yellow"))

        #播放音乐
        if pygame.mixer.music.get_busy() == False:
            pygame.mixer.music.load(Game_config.sound_start)
            pygame.mixer.music.play()
        #监听消息查看是否点击了开始游戏
        if  pygame.MOUSEBUTTONDOWN in keys:
            #响应了当前点击事件之后马上把这个事件删除
            keys.remove(pygame.MOUSEBUTTONDOWN)
            pos = pygame.mouse.get_pos()
            if pos[0] >= Game_config.button_start_min_x and pos[0] <=Game_config.button_start_max_x and \
            pos[1] >= Game_config.button_start_min_y and pos[1] <= Game_config.button_start_max_y:
                screen = Screen.screen_game.value
                Goods_Generate(Game_config.level)





    #游戏界面
    if screen == Screen.screen_game.value:
        #绘制游戏主体背景
        picture = gamebox.from_image(500, 380, picture_list_screen[Game_Background.background_game_body.value])
        #游戏背景头
        picture2 = gamebox.from_image(500, 37.5, picture_list_screen[Game_Background.background_game_heard.value])

        picture2.scale_by(0.6)
        picture.scale_by(0.7)
        camera.draw(picture)
        camera.draw(picture2)
        if pygame.MOUSEBUTTONDOWN in keys:
            # 响应了当前点击事件之后马上把这个事件删除
            keys.remove(pygame.MOUSEBUTTONDOWN)
            pos = pygame.mouse.get_pos()
            if pos[0] >= 786 and pos[0] <= 881 and \
                    pos[1] >= 7 and pos[1] <= 67 :
                Game_config.counter = 0

        #绘制人物角色
        if chain_thrown_out_catchsomething :
            character_getthing.image = picture_list_character_getthing[character_index]
            camera.draw(character_getthing)
            # 语气词语
            camera.draw(hard_object)
        else:
            camera.draw(character)

        if character_index == 0:
            character_index += 1
        else:
            character_index = 0


        if chain_thrown_out == False:
            #绘制绳子左右摆动的声音
            if pygame.mixer.music.get_busy() == False:
                pygame.mixer.music.load(Game_config.sound__rope_swing)
                pygame.mixer.music.play()
            if Game_config.popped_up_word_counterdown >= 16:
                if direction_left_to_right == True:
                    Game_config.radins += Game_config.radins_offset
                else:
                    Game_config.radins -= Game_config.radins_offset

            if Game_config.radins <= Game_config.radins_min:
               direction_left_to_right = True
            if Game_config.radins >= Game_config.radins_max:
                direction_left_to_right = False

            # 绘制钩子
            if Game_config.radins in hook_dict:
                chainhead.image = hook_dict[Game_config.radins]
            chainhead.x = 500 + math.sin(Game_config.radins / 57.29) * 75
            chainhead.y = 75 + math.cos(Game_config.radins / 57.29) * 75
            camera.draw(chainhead)

            # 绳子的本体
            item = gamebox.from_color(500, 75, "black", 5, 5)
            for i in range(0, 26):
                item = gamebox.from_color(500 + math.sin(Game_config.radins / 57.29) * 2.5 * i,
                                          75 + math.cos(Game_config.radins / 57.29) * 2.5 * i, "black", 5, 5)
                camera.draw(item)

        # 按下向下的方向键的时候
        if pygame.K_DOWN in keys and chain_thrown_out == False and Game_config.popped_up_word_counterdown >= 16:
            chain_thrown_out = True
            chain_thrown_out_away = True
            chain_thrown_out_catchsomething = False
            # 抓东西的时候角色放大了1.2倍
            #character.scale_by(1.2)

        #按下向下的方向键绳子慢慢边长
        if chain_thrown_out == True and chain_thrown_out_away == True:
            Game_config.chain_distance += Game_config.speed
            #绳子变长时候的音乐
            pygame.mixer.music.stop()
            if pygame.mixer.music.get_busy() == False:
                pygame.mixer.music.load(Game_config.sound_go_long)
                pygame.mixer.music.play()
            #把绳子慢慢增长有多长就画多少个黑色像素
            for i in range(1, Game_config.chain_distance):
                item = gamebox.from_color(500 + math.sin(Game_config.radins / 57.29) * 2.5 * i,
                                          75 + math.cos(Game_config.radins / 57.29) * 2.5 * i, "black", 5, 5)
                camera.draw(item)
            chainhead.x = 500 + math.sin(Game_config.radins / 57.29) * (10 + 2.5 * Game_config.chain_distance)
            chainhead.y = 75 + math.cos(Game_config.radins / 57.29) * (10 + 2.5 * Game_config.chain_distance)
            if Game_config.radins in hook_dict:
                chainhead.image = hook_dict[Game_config.radins]
            camera.draw(chainhead)
        #到低了绳子开始收缩
        if chain_thrown_out == True and chain_thrown_out_away == False:
            Game_config.chain_distance -= Game_config.weight_item_caught
            for i in range(1, Game_config.chain_distance):
                item = gamebox.from_color(500 + math.sin(Game_config.radins / 57.29) * 2.5 * i,
                                          75 + math.cos(Game_config.radins / 57.29) * 2.5 * i, "black", 5, 5)
                camera.draw(item)
            chainhead.x = 500 + math.sin(Game_config.radins / 57.29) * (10 + 2.5 * Game_config.chain_distance)
            chainhead.y = 75 + math.cos(Game_config.radins / 57.29) * (10 + 2.5 * Game_config.chain_distance)
            camera.draw(chainhead)


        # 钩子超过界面的边界了
        if chainhead.x < 0 or chainhead.x > 1000 or chainhead.y > 700:
            chain_thrown_out_away = False
        #抓到东西到最上面了,金钱要增加
        if Game_config.chain_distance <= 29 and chain_thrown_out == True:
            if chain_thrown_out_catchsomething == True:
                # 金钱增加的声音
                pygame.mixer.music.stop()
                if pygame.mixer.music.get_busy() == False:
                    pygame.mixer.music.load(Game_config.sound__money_increase)
                    pygame.mixer.music.play()
                if value_caught != 0:
                    Game_config.popped_up_word_counterdown = 1
                Game_config.money += value_caught
                chain_thrown_out_catchsomething = False
            chain_thrown_out = False
            #角色缩小0.833
            #character.scale_by(0.833)
            Game_config.weight_item_caught = Game_config.speed + 5


  #操作游戏的时候物品的变化
        for gold in gold_middle_list:
            if gold.touches(chainhead) and chain_thrown_out_catchsomething == False:
                chain_thrown_out_away = False
                chain_thrown_out_catchsomething = True
                Game_config.weight_item_caught = Game_config.speed - 2
                Game_config.pict_index = Mineral.gold_middle.value
                value_caught = Game_config.gold_middle_value
                gold_middle_list.remove(gold)
            camera.draw(gold)

        for gold in gold_small_list:
            if gold.touches(chainhead) and chain_thrown_out_catchsomething == False:
                chain_thrown_out_away = False
                chain_thrown_out_catchsomething = True
                Game_config.weight_item_caught = Game_config.speed
                Game_config.pict_index = Mineral.gold_small.value
                if Store.item_gold_modifer == True:
                    value_caught = 125 + 25 * Game_config.level
                else:
                    value_caught = Game_config.gold_small_value
                gold_small_list.remove(gold)
            camera.draw(gold)

        for gold in gold_large_list:
            if gold.touches(chainhead) and chain_thrown_out_catchsomething == False:
                chain_thrown_out_away = False
                chain_thrown_out_catchsomething = True
                Game_config.weight_item_caught = Game_config.speed - 4
                Game_config.pict_index = Mineral.gold_middle.gold_big.value
                value_caught = Game_config.gold_large_value
                gold_large_list.remove(gold)
                #抓到大黄金声音
                pygame.mixer.music.stop()
                if pygame.mixer.music.get_busy() == False:
                    pygame.mixer.music.load(Game_config.sound_get_big_gold)
                    pygame.mixer.music.play()
            camera.draw(gold)


        for gold in gold_dimaond_list:
            if gold.touches(chainhead) and chain_thrown_out_catchsomething == False:
                chain_thrown_out_away = False
                chain_thrown_out_catchsomething = True
                Game_config.weight_item_caught = Game_config.speed + 4
                Game_config.pict_index = Mineral.Diamond.value
                gold_dimaond_list.remove(gold)
                if Store.item_polisher == True:
                    value_caught = Game_config.dimaond_value + 300 + 75 * Game_config.level
                else:
                    value_caught = Game_config.dimaond_value + 50 * Game_config.level

            camera.draw(gold)


        for gold in gold_small_rock_list:
            if gold.touches(chainhead) and chain_thrown_out_catchsomething == False:
                chain_thrown_out_away = False
                chain_thrown_out_catchsomething = True
                Game_config.weight_item_caught = Game_config.speed - 2
                Game_config.pict_index = Mineral.stone_small.value
                value_caught = Game_config.small_rock_value
                if Store.item_rocks == True:
                    value_caught = value_caught * 4
                gold_small_rock_list.remove(gold)
                # 抓到石头
                pygame.mixer.music.stop()
                if pygame.mixer.music.get_busy() == False:
                    pygame.mixer.music.load(Game_config.sound_get_stone)
                    pygame.mixer.music.play()
            camera.draw(gold)

        for gold in gold_big_rock_list:
            if gold.touches(chainhead) and chain_thrown_out_catchsomething == False:
                chain_thrown_out_away = False
                chain_thrown_out_catchsomething = True
                Game_config.weight_item_caught = Game_config.speed - 4
                Game_config.pict_index = Mineral.stone_big.value
                Game_config.value_caught = Game_config.big_rock_value
                if Store.item_rocks == True:
                    value_caught = value_caught * 4
                gold_big_rock_list.remove(gold)
                # 抓到石头
                pygame.mixer.music.stop()
                if pygame.mixer.music.get_busy() == False:
                    pygame.mixer.music.load(Game_config.sound_get_stone)
                    pygame.mixer.music.play()
            camera.draw(gold)

        for gold in gold_random_big_list:
            if gold.touches(chainhead) and chain_thrown_out_catchsomething == False:
                if chain_thrown_out_catchsomething == False:
                    Game_config.weight_item_caught = Game_config.speed + random.randint(-4, 5)
                chain_thrown_out_away = False
                chain_thrown_out_catchsomething = True
                Game_config.pict_index = Mineral.random_big.value
                if Store.item_luck == True:
                    value_caught = random.randint(500, 1000)
                else:
                    value_caught = random.randint(-300, 700)
                gold_random_big_list.remove(gold)
            camera.draw(gold)

        #抓到了物品,要把物品挂在钩子上
        if chain_thrown_out_catchsomething == True:

            item = gamebox.from_image(500 + math.sin(Game_config.radins / 57.29) * 2.5 * (Game_config.chain_distance+10),
                                      75 + math.cos(Game_config.radins / 57.29) * 2.5 * (Game_config.chain_distance+10), picture_mineral[ Game_config.pict_index])
            camera.draw(item)

        #绘制游戏的参数的变化
        Game_config.counter -= 1
        camera.draw(
            gamebox.from_text(850, 100, "游戏关卡:" + str(Game_config.level) + " 目标金额:" + str(Game_config.money_goal[Game_config.level - 1]), "SimHei",
                              20, "yellow"))
        camera.draw(gamebox.from_text(135, 25, "你的金钱:" +"$"+ str(Game_config.money), "SimHei", 24, "yellow"))
        camera.draw(gamebox.from_text(130, 55, "剩余时间:" + str(int(Game_config.counter / 15)), "SimHei", 22, "red"))

        Game_config.popped_up_word_counterdown += 1
        if Game_config.popped_up_word_counterdown <= 15:
            if value_caught > 0:
                camera.draw(gamebox.from_text(300, 25, "+" +"$"+ str(value_caught), "arial", 30, "green", bold=True))
            else:
                camera.draw(gamebox.from_text(300, 25,  str(value_caught), "arial", 30, "red", bold=True))

        elif Game_config.popped_up_word_counterdown == 16:
            value_caught = 0

#游戏时间结束之后的处理
        if Game_config.counter <= 0:
            if Game_config.money < Game_config.money_goal[Game_config.level - 1] or Game_config.level == 12 :
                #进入游戏失败的界面
                screen = Screen.screen_failed.value

                #否则进入游戏商城界面
            else:
                Game_config.level += 1
                # shop generations
                random_list = [0, 1, 2, 3, 4, 5, 6]
                random.shuffle(random_list)
                shop_list = []
                shop_price = []
                shop_selection = 0
                shop_list.append(random_list[0])
                shop_list.append(random_list[1])
                shop_list.append(random_list[2])
                # 商店的商品价格随着等级的增高
                # 相应的要变贵
                # 变化的范围是 基础价格+ 等级*10  到 基础价格+等级*150之间
                for stuff in shop_list:
                    if Game_config.level != 12:
                        if stuff == 0:
                            shop_price.append(random.randint(25 + Game_config.level * 10, 300 + Game_config.level * 50))
                        elif stuff == 1:
                            shop_price.append(random.randint(400, 600 + Game_config.level * 50))
                        elif stuff == 2:
                            shop_price.append(random.randint(50 + Game_config.level * 20, 300 + Game_config.level * 30))
                        elif stuff == 3:
                            shop_price.append(random.randint(200 + Game_config.level * 20, 600 + Game_config.level * 75))
                        elif stuff == 4:
                            shop_price.append(random.randint(50 + Game_config.level * 20, 200 + Game_config.level * 50))
                        elif stuff == 5:
                            shop_price.append(random.randint(50 + Game_config.level * 30, 400 + Game_config.level * 150))
                        else:
                            shop_price.append(random.randint(5 + Game_config.level * 20, 200 + Game_config.level * 10))
                    else:
                        shop_price.append(0)
                #游戏结束回复游戏的初始参数
                Game_config.speed = 5
                Store.item_gold_modifer = False
                Store.item_polisher = False
                Store.item_lamp = False
                Store.item_time = False
                Store.item_luck = False
                Store.item_rocks = False
                #进入商城界面
                screen = Screen.screen_shop.value
                chain_thrown_out_catchsomething = False
                chain_thrown_out = False
                #character.scale_by(0.833)
                Game_config.weight_item_caught = Game_config.speed + 5
                value_caught = 0


    if screen == Screen.screen_shop.value:
        #播放音乐
        if pygame.mixer.music.get_busy() == False:
            pygame.mixer.music.load(Game_config.sound_start)
            pygame.mixer.music.play()
        picture = gamebox.from_image(500, 360, picture_list_screen[Game_Background.background_shop.value])
        picture.scale_by(0.8)
        camera.draw(picture)
        if shop_list[0] != -1:
           camera.draw(gamebox.from_image(100, 430, dict_goods_picture[shop_list[0]]))
           camera.draw(gamebox.from_text(110, 550, "$" + str(shop_price[0]), "SimHei", 22, "green"))
           camera.draw(gamebox.from_text(180, 650, "商品1:" + dict_goods_text[shop_list[0]], "SimHei", 16, "yellow"))
        if shop_list[1] != -1:
            camera.draw(gamebox.from_image(300, 430, dict_goods_picture[shop_list[1]]))
            camera.draw(gamebox.from_text(310, 550, "$" + str(shop_price[1]), "SimHei", 22, "green"))
            camera.draw(gamebox.from_text(430, 650, "商品2:" + dict_goods_text[shop_list[1]], "SimHei", 16, "yellow"))
        if shop_list[2] != -1:
            camera.draw(gamebox.from_image(500, 430, dict_goods_picture[shop_list[2]]))
            camera.draw(gamebox.from_text(510, 550, "$" + str(shop_price[2]), "SimHei", 22, "green"))
            camera.draw(gamebox.from_text(780, 650, "商品3:" + dict_goods_text[shop_list[2]], "SimHei", 16, "yellow"))

        camera.draw(
            gamebox.from_text(800, 50, "下一个关卡:" + str(Game_config.level) +"  "+ "目标金额:" + str(Game_config.money_goal[Game_config.level - 1]),
                              "SimHei",
                              20, "yellow"))
        camera.draw(gamebox.from_text(850, 25, "你的金钱:" +"$"+ str(Game_config.money), "SimHei", 20, "green"))
        if Game_config.level == 12:
            camera.draw(gamebox.from_text(500, 150, "最后一关了所有商品免费", "SimHei", 35, "green",bold=True))
        if pygame.MOUSEBUTTONDOWN in keys:
            # 响应了当前点击事件之后马上把这个事件删除
            keys.remove(pygame.MOUSEBUTTONDOWN)
            pos = pygame.mouse.get_pos()
            if pos[0] >= 62 and pos[0] <= 141 and \
                    pos[1] >= 369 and pos[1] <= 502 and shop_list[0] != -1:
                Game_config.money -= shop_price[0]
                Set_Store_value(shop_list[0])
                shop_list[0] = -1
                # 播放音乐
                pygame.mixer.music.load(Game_config.sound__money_increase)
                pygame.mixer.music.play()

            if pos[0] >= 227 and pos[0] <= 362 and \
                    pos[1] >= 372 and pos[1] <= 494 and shop_list[1] != -1:
                Game_config.money -= shop_price[1]
                Set_Store_value(shop_list[1])
                shop_list[1] = -1
                pygame.mixer.music.load(Game_config.sound__money_increase)
                pygame.mixer.music.play()

            if pos[0] >= 438 and pos[0] <= 596 and \
                    pos[1] >= 363 and pos[1] <= 496 and shop_list[2] != -1:
                Game_config.money -= shop_price[2]
                Set_Store_value(shop_list[2])
                shop_list[2] = -1
                pygame.mixer.music.load(Game_config.sound__money_increase)
                pygame.mixer.music.play()

            if pos[0] >= 767 and pos[0] <= 899 and \
                    pos[1] >= 122 and pos[1] <= 180 :
                screen = Screen.screen_game.value
                Goods_Generate(Game_config.level)




    if screen == Screen.screen_target_score.value:
       pass

    if screen == Screen.screen_failed.value:
        picture = gamebox.from_image(500, 360, picture_list_screen[Game_Background.background_failed.value])
        picture.scale_by(0.9)
        camera.draw(picture)
        camera.draw(gamebox.from_text(500, 400, "你的总分数是:" +str(Game_config.money), "SimHei", 40, "red"))
        if pygame.MOUSEBUTTONDOWN in keys:
            # 响应了当前点击事件之后马上把这个事件删除
            keys.remove(pygame.MOUSEBUTTONDOWN)
            pos = pygame.mouse.get_pos()
            if pos[0] >= 78 and pos[0] <= 216 and \
                    pos[1] >= 619 and pos[1] <= 673 :
                screen = Screen.screen_game.value
                Game_config.level = 1
                print(1)
                Goods_Generate(Game_config.level)
                Game_config.money = 0


                #绘制游戏界面
    camera.display()






#每个关卡游戏界面物品的生成

# 钻石的基础数量是0
# 如果在商店买了钻石水 3
# 如果等级能被4整除
# 钻石的数量 no_diamond += 2 +int(level/3)
# 如果等级大于6
# 钻石的数量 no_diamond += 2
# 如果等级大于等于8
# 钻石的数量 no_diamond += 4
# 如果等级大于等于12
# 钻石的数量 no_diamond += 10

# 神秘包裹的数量
# 如果等级能被2整除 no_random += 3 +int(level/2)
# 否则 no_random = 1

# 大黄金的数量
# 如果等级大于等于3no_big = 1 no_big += level//2
# 否则大黄金数量等于0
# 如果等级大于等于6 no_big += 2
# 如果等级大于等于8 no_big += 3
# 如果等级大于等于12 no_big = 6

# base 一些物品的最小基础值
# 如果等级大于等于6 base = 1 否则 base = 0
# 如果等级大于等于8 base = 2
# 如果等级大于等于12 base = 5

# 如果等级大于等于8 小石头的数量[0,2]
# 如果等级大于等于8 小黄金的数量[0,2或者3]

# 如果等级小于8 小石头的数量 [5,7]
# 如果等级小于8 小黄金的数量 5 + level // 2, 12 + level // 2

# 中等黄金的数量
# 0, random.randint(5+level//2, 8+level//2)

# 大石头的数量
# 0, random.randint(1 + level // 4 + base, 3 + level // 4 + base)

# 大黄金的数量
# 0, random.randint(base, no_big)

# 钻石的数量
# 0, random.randint(base, no_diamond)

# 神秘包裹的数量
# 0, random.randint(base, no_random)

# 游戏的fps
# ticks_per_second = 30 表示是1000/30 的刷新频率

# 如果生成的物品模型发生了重叠,那么要重新生成
def Goods_Generate(level):
    global  gold_middle_list,gold_small_list,gold_large_list,gold_dimaond_list,gold_small_rock_list
    global  gold_big_rock_list,gold_random_big_list
    gold_middle_list =[]
    gold_small_list = []
    gold_large_list = []
    gold_dimaond_list = []
    gold_small_rock_list = []
    gold_big_rock_list = []
    gold_random_big_list = []
    #counter控制时间,每局的游戏60s
    if Store.item_time == True:
        Game_config.counter = 1200
    else:
        Game_config.counter = 900

    #gold_small_rock_list = []

    if Store.item_lamp == True:
        no_diamond = 3
    else:
        no_diamond = 0


    if level % 4 == 0:
        no_diamond += 2
        no_diamond += int(level/3)

    if level % 2 == 0:
        no_random = 3
        no_random += int(level/2)
    else:
        no_random = 1

    if level >= 3:
        no_big = 1
        no_big += level//2
    else:
        no_big = 0

    if level >= 6:
        no_diamond += 2
        no_big += 2
        no_random += 2
        base = 1
    else:
        base = 0

    if level >= 8:
        no_diamond += 4
        no_big += 3
        no_random += 3
        base = 2
    if level == 12:
        no_diamond += 10
        base = 5
        no_big = 6
        no_random = 6

    try_index = Game_config.try_times
    if level >= 8:
        for c in range(0, random.randint(2,3)):
            item = gamebox.from_image(random.randint(Game_config.mineral_race_min_x, Game_config.mineral_race_max_x), random.randint(Game_config.mineral_race_min_y, Game_config.mineral_race_max_y), picture_mineral[Mineral.gold_small.value])
            gold_small_list.append(item)
        for c in range(0, 2):
            touched = True
            while touched and try_index >=0:
                item = gamebox.from_image(random.randint(Game_config.mineral_race_min_x, Game_config.mineral_race_max_x), random.randint(Game_config.mineral_race_min_y, Game_config.mineral_race_max_y), picture_mineral[Mineral.stone_small.value])
                touched = False
                for evaluated_item in gold_small_list:
                    if item.touches(evaluated_item) == True:
                        touched = True
                for evaluated_item in gold_small_rock_list:
                    if item.touches(evaluated_item) == True:
                        touched = True
                try_index -= 1
            if try_index >= 0 :
                gold_small_rock_list.append(item)
            try_index = Game_config.try_times


    else:
        for c in range(0, random.randint(5 + level // 2, 12 + level // 2)):
            item = gamebox.from_image(random.randint(Game_config.mineral_race_min_x, Game_config.mineral_race_max_x), random.randint(Game_config.mineral_race_min_y, Game_config.mineral_race_max_y), picture_mineral[Mineral.gold_small.value])
            gold_small_list.append(item)
        for c in range(0, random.randint(5, 7)):
            touched = True
            while touched and try_index >=0:
                item = gamebox.from_image(random.randint(Game_config.mineral_race_min_x, Game_config.mineral_race_max_x), random.randint(Game_config.mineral_race_min_y, Game_config.mineral_race_max_y), picture_mineral[Mineral.stone_small.value])
                touched = False
                for evaluated_item in gold_small_list:
                    if item.touches(evaluated_item) == True:
                        touched = True
                for evaluated_item in gold_small_rock_list:
                    if item.touches(evaluated_item) == True:
                        touched = True
                try_index -= 1

            if try_index >= 0 :
                gold_small_rock_list.append(item)
            try_index = Game_config.try_times


    for c in range(0, random.randint(5+level//2, 8+level//2)):
        touched = True
        while touched and try_index >=0:
            item = gamebox.from_image(random.randint(Game_config.mineral_race_min_x, Game_config.mineral_race_max_x), random.randint(Game_config.mineral_race_min_y, Game_config.mineral_race_max_y), picture_mineral[Mineral.gold_middle.value])
            touched = False
            for evaluated_item in gold_small_list:
                if item.touches(evaluated_item) == True:
                    touched = True
            for evaluated_item in gold_small_rock_list:
                if item.touches(evaluated_item) == True:
                    touched = True
            for evaluated_item in gold_middle_list:
                if item.touches(evaluated_item) == True:
                    touched = True
            try_index -= 1

        if try_index >= 0:
            gold_middle_list.append(item)
        try_index = Game_config.try_times

    for c in range(0, random.randint(1+level//4+base, 3+level//4+base)):
        touched = True
        while touched and try_index >=0:
            item = gamebox.from_image(random.randint(Game_config.mineral_race_min_x, Game_config.mineral_race_max_x), random.randint(Game_config.mineral_race_min_y, Game_config.mineral_race_max_y), picture_mineral[Mineral.stone_big.value])
            touched = False
            for evaluated_item in gold_small_list:
                if item.touches(evaluated_item) == True:
                    touched = True
            for evaluated_item in gold_small_rock_list:
                if item.touches(evaluated_item) == True:
                    touched = True
            for evaluated_item in gold_middle_list:
                if item.touches(evaluated_item) == True:
                    touched = True
            for evaluated_item in gold_big_rock_list:
                if item.touches(evaluated_item) == True:
                    touched = True
            try_index -= 1
        if try_index >= 0:
            gold_big_rock_list.append(item)
        try_index = Game_config.try_times


    for c in range(0, random.randint(base, no_big)):
        touched = True
        while touched and try_index >=0:
            item = gamebox.from_image(random.randint(Game_config.mineral_race_min_x, Game_config.mineral_race_max_x), random.randint(Game_config.mineral_race_min_y, Game_config.mineral_race_max_y), picture_mineral[Mineral.gold_big.value])
            touched = False
            for evaluated_item in gold_small_list:
                if item.touches(evaluated_item) == True:
                    touched = True
            for evaluated_item in gold_small_rock_list:
                if item.touches(evaluated_item) == True:
                    touched = True
            for evaluated_item in gold_middle_list:
                if item.touches(evaluated_item) == True:
                    touched = True
            for evaluated_item in gold_big_rock_list:
                if item.touches(evaluated_item) == True:
                    touched = True
            for evaluated_item in gold_large_list:
                if item.touches(evaluated_item) == True:
                    touched = True
            try_index -= 1

        if try_index >= 0:
            gold_large_list.append(item)
        try_index = Game_config.try_times


    for c in range(0, random.randint(base, no_diamond)):
        touched = True
        while touched and try_index >=0:
            item = gamebox.from_image(random.randint(Game_config.mineral_race_min_x, Game_config.mineral_race_max_x), random.randint(Game_config.mineral_race_min_y, Game_config.mineral_race_max_y), picture_mineral[Mineral.Diamond.value])
            touched = False
            for evaluated_item in gold_small_list:
                if item.touches(evaluated_item) == True:
                    touched = True
            for evaluated_item in gold_small_rock_list:
                if item.touches(evaluated_item) == True:
                    touched = True
            for evaluated_item in gold_middle_list:
                if item.touches(evaluated_item) == True:
                    touched = True
            for evaluated_item in gold_big_rock_list:
                if item.touches(evaluated_item) == True:
                    touched = True
            for evaluated_item in gold_large_list:
                if item.touches(evaluated_item) == True:
                    touched = True
            for evaluated_item in gold_dimaond_list:
                if item.touches(evaluated_item) == True:
                    touched = True
            try_index -= 1
        gold_dimaond_list.append(item)
        if try_index >= 0:
            gold_dimaond_list.append(item)
        try_index = Game_config.try_times

    for c in range(0, random.randint(base, no_random)):
        touched = True
        while touched and try_index >=0:
            item = gamebox.from_image(random.randint(Game_config.mineral_race_min_x, Game_config.mineral_race_max_x), random.randint(Game_config.mineral_race_min_y, Game_config.mineral_race_max_y), picture_mineral[Mineral.random_big.value])
            touched = False
            for evaluated_item in gold_small_list:
                if item.touches(evaluated_item) == True:
                    touched = True
            for evaluated_item in gold_small_rock_list:
                if item.touches(evaluated_item) == True:
                    touched = True
            for evaluated_item in gold_middle_list:
                if item.touches(evaluated_item) == True:
                    touched = True
            for evaluated_item in gold_big_rock_list:
                if item.touches(evaluated_item) == True:
                    touched = True
            for evaluated_item in gold_large_list:
                if item.touches(evaluated_item) == True:
                    touched = True
            for evaluated_item in gold_dimaond_list:
                if item.touches(evaluated_item) == True:
                    touched = True
            for evaluated_item in gold_random_big_list:
                if item.touches(evaluated_item) == True:
                    touched = True
            try_index -= 1

        if try_index >= 0:
            gold_random_big_list.append(item)
        try_index = Game_config.try_times


if __name__ == '__main__':
    ticks_per_second = 20
    gamebox.timer_loop(ticks_per_second, Callback_Loop)




gamebox.py 文件

# A library file for simplifying pygame interaction.  You MUST place this file in the same directory as your game py files.

'''This code is the original work of Luther Tychonievich, who releases it
into the public domain.

As a courtesy, Luther would appreciate it if you acknowledged him in any work
that benefited from this code.'''

from __future__ import division
import pygame, sys
import urllib, os.path

if 'urlretrieve' not in dir(urllib):
    from urllib.request import urlretrieve as _urlretrieve
else:
    _urlretrieve = urllib.urlretrieve

pygame.init()

# a cache to avoid loading images many time
_known_images = {}
_known_sounds = {}


def _image(key, flip=False, w=0, h=0, angle=0):
    '''A method for loading images, caching them, and flipping them'''
    if '__hash__' not in dir(key):
        key = id(key)
    angle, w, h = int(angle), int(w), int(h)
    ans = None
    if (key, flip, w, h, angle) in _known_images:
        ans = _known_images[(key, flip, w, h, angle)]
    elif angle != 0:
        base = _image(key, flip, w, h)
        img = pygame.transform.rotozoom(base, angle, 1)
        _known_images[(key, flip, w, h, angle)] = img
        ans = img
    elif w != 0 or h != 0:
        base = _image(key, flip)
        img = pygame.transform.smoothscale(base, (w, h))
        _known_images[(key, flip, w, h, angle)] = img
        ans = img
    elif flip:
        base = _image(key)
        img = pygame.transform.flip(base, True, False)
        _known_images[(key, flip, w, h, angle)] = img
        ans = img
    else:
        img, _ = _get_image(key)
        _known_images[(key, flip, w, h, angle)] = img
        ans = img
    if w == 0 and h == 0:
        if angle != 0:
            tmp = _image(key, flip, w, h)
        else:
            tmp = ans
        _known_images[(key, flip, tmp.get_width(), tmp.get_height(), angle)] = ans
    return ans


def _image_from_url(url):
    '''a method for loading images from urls by first saving them locally'''
    filename = os.path.basename(url)
    if not os.path.exists(filename):
        if '://' not in url: url = 'http://' + url
        _urlretrieve(url, filename)
    image, filename = _image_from_file(filename)
    return image, filename


def _image_from_file(filename):
    '''a method for loading images from files'''
    image = pygame.image.load(filename).convert_alpha()
    _known_images[filename] = image
    _known_images[(image.get_width(), image.get_height(), filename)] = image
    return image, filename


def _get_image(thing):
    '''a method for loading images from cache, then file, then url'''
    if thing in _known_images: return _known_images[thing], thing
    sid = '__id__' + str(id(thing))
    if sid in _known_images: return _known_images[sid], sid
    if type(thing) is str:
        if os.path.exists(thing): return _image_from_file(thing)
        return _image_from_url(thing)
    _known_images[sid] = thing
    _known_images[(thing.get_width(), thing.get_height(), sid)] = thing
    return thing, sid


def load_sprite_sheet(url_or_filename, rows, columns):
    '''Loads a sprite sheet. Assumes the sheet has rows-by-columns evenly-spaced images and returns a list of those images.'''
    sheet, key = _get_image(url_or_filename)
    height = sheet.get_height() / rows
    width = sheet.get_width() / columns
    frames = []
    for row in range(rows):
        for col in range(columns):
            #把动图做一个切片切成均匀大小
            clip = pygame.Rect(col * width, row * height, width, height)
            frame = sheet.subsurface(clip)
            frames.append(frame)
    return frames


__all__ = ['load_sprite_sheet']


def from_image(x, y, filename_or_url):
    '''Creates a SpriteBox object at the given location from the provided filename or url'''
    image, key = _get_image(filename_or_url)
    return SpriteBox(x, y, image, None)


__all__.append('from_image')


def from_color(x, y, color, width, height):
    '''Creates a SpriteBox object at the given location with the given color, width, and height'''
    return SpriteBox(x, y, None, color, width, height)


__all__.append('from_color')


def from_text(x, y, text, fontname, fontsize, color, bold=False, italic=False):
    '''Creates a SpriteBox object at the given location with the given text as its content'''
    font = pygame.font.match_font(fontname.replace(" ", "").lower())
    if font is None:
        sys.stderr.write("ERROR: no font named " + fontname + "; using default font instead")
    font = pygame.font.Font(font, fontsize)
    font.set_bold(bold)
    font.set_italic(italic)
    if type(color) is str: color = pygame.Color(color)
    return from_image(x, y, font.render(text, True, color))


__all__.append('from_text')


def load_sound(url_or_filename):
    '''Reads a sound file from a given filename or url'''
    if url_or_filename in _known_images: return _known_sounds[url_or_filename]
    if not os.path.exists(url_or_filename):
        filename = os.path.basename(url_or_filename)
        if not os.path.exists(filename):
            _urlretrieve(url_or_filename, filename)
        url_or_filename = filename
    sound = pygame.mixer.Sound(url_or_filename)
    _known_sounds[url_or_filename] = sound
    return sound


__all__.append('load_sound')


class Camera(object):
    '''A camera defines what is visible. It has a width, height, full screen status,
    and can be moved. Moving a camera changes what is visible.
    '''
    is_initialized = False

    #    __slots__ = ["_surface", "x", "y", "speedx", "speedy"]
    def __init__(self, width, height, full_screen=False):
        '''Camera(pixelsWide, pixelsTall, False) makes a window; using True instead makes a full-screen display.'''
        if Camera.is_initialized: raise Exception("You can only have one Camera at a time")
        # if height > 768: raise Exception("The Game Expo screens will only be 768 pixels tall")
        # if width > 1366: raise Exception("The Game Expo screens will only be 1366 pixels wide")
        if full_screen:
            self.__dict__['_surface'] = pygame.display.set_mode([width, height], pygame.FULLSCREEN)
        else:
            self.__dict__['_surface'] = pygame.display.set_mode([width, height])
        self.__dict__['_x'] = 0
        self.__dict__['_y'] = 0
        Camera.is_initialized = True

    def move(self, x, y=None):
        '''camera.move(3, -7) moves the screen's center to be 3 more pixels to the right and 7 more up'''
        if y is None: x, y = x
        self.x += x
        self.y += y

    def draw(self, thing, *args):
        '''camera.draw(box) draws the provided SpriteBox object
        camera.draw(image, x, y) draws the provided image centered at the provided coordinates
        camera.draw("Hi", "Arial", 12, "red", x, y) draws the text Hi in a red 12-point Arial font at x,y'''
        if isinstance(thing, SpriteBox):
            thing.draw(self)
        elif isinstance(thing, pygame.Surface):
            try:
                if len(args) == 1:
                    x, y = args[0]
                else:
                    x, y = args[:2]
                self._surface.blit(thing, [x - thing.get_width() / 2, y - thing.get_height() / 2])
            except e:
                raise Exception("Wrong arguments; try .draw(surface, [x,y])")
        elif type(thing) is str:
            try:
                font = pygame.font.match_font(args[0].replace(" ", "").lower())
                if font is None:
                    sys.stderr.write("ERROR: no font named " + fontname + "; using default font instead")
                size = args[1]
                color = args[2]
                if type(color) is str: color = pygame.Color(color)
                self.draw(pygame.font.Font(font, size).render(thing, True, color), *args[3:])
            except e:
                raise Exception("Wrong arguments; try .draw(text, fontName, fontSize, color, [x,y])")
        else:
            raise Exception("I don't know how to draw a ", type(thing))

    def display(self):
        '''Causes what has been drawn recently by calls to draw(...) to be displayed on the screen'''
        pygame.display.flip()

    def clear(self, color):
        '''Erases the screen by filling it with the given color'''
        if type(color) is str: color = pygame.Color(color)
        self._surface.fill(color)

    def __getattr__(self, name):
        if name in self.__dict__: return self.__dict__[name]
        x, y, w, h = self._x, self._y, self._surface.get_width(), self._surface.get_height()
        if name == 'left': return x
        if name == 'right': return x + w
        if name == 'top': return y
        if name == 'bottom': return y + h
        if name == 'x': return x + w / 2
        if name == 'y': return y + h / 2
        if name == 'center': return x + w / 2, y + h / 2
        if name == 'topleft': return x, y
        if name == 'topright': return x + w, y
        if name == 'bottomleft': return x, y + h
        if name == 'bottomright': return x + w, y + h
        if name == 'width': return w
        if name == 'height': return h
        if name == 'size': return w, h
        if name == 'mousex': return pygame.mouse.get_pos()[0] + self._x
        if name == 'mousey': return pygame.mouse.get_pos()[1] + self._y
        if name == 'mouse': return pygame.mouse.get_pos()[0] + self._x, pygame.mouse.get_pos()[1] + self._y
        if name == 'mouseclick': return any(pygame.mouse.get_pressed())
        raise Exception("There is no '" + name + "' in a Camera object")

    def __setattr__(self, name, value):
        if name in self.__dict__:
            self.__dict__[name] = value
            return
        w, h = self._surface.get_width(), self._surface.get_height()
        if name == 'left':
            self._x = value
        elif name == 'right':
            self._x = value - w
        elif name == 'top':
            self._y = value
        elif name == 'bottom':
            self._y = value - h
        elif name == 'x':
            self._x = value - w / 2
        elif name == 'y':
            self._y = value - h / 2
        elif name == 'center':
            self._x, self._y = value[0] - w / 2, value[1] - h / 2
        elif name == 'topleft':
            self._x, self._y = value[0], value[1]
        elif name == 'topright':
            self._x, self._y = value[0] - w, value[1]
        elif name == 'bottomleft':
            self._x, self._y = value[0], value[1] - h
        elif name == 'bottomright':
            self._x, self._y = value[0] - w, value[1] - h
        elif name in ['width', 'height', 'size', 'mouse', 'mousex', 'mousey', 'mouseclick']:
            raise Exception("You cannot change the '" + name + "' of a Camera object")
        else:
            sys.stderr.write("creating field named " + name)
            self.__dict__[name] = value

    def __repr__(self):
        return str(self)

    def __str__(self):
        return '%dx%d Camera centered at %d,%d' % (self.width, self.height, self.x, self.y)


__all__.append('Camera')


class SpriteBox(object):
    '''Intended to represent a sprite (i.e., an image that can be drawn as part of a larger view) and the box that contains it. Has various collision and movement methods built in.'''

    #    __slots__ = ["x","y","speedx","speedy","_w","_h","_key","_image","_color"]
    def __init__(self, x, y, image, color, w=None, h=None):
        '''You should probably use the from_image, from_text, or from_color method instead of this one'''
        self.__dict__['x'] = x
        self.__dict__['y'] = y
        self.__dict__['speedx'] = 0
        self.__dict__['speedy'] = 0
        if image is not None:
            self._set_key(image, False, 0, 0, 0)
            if w is not None:
                if h is not None:
                    self.size = w, h
                else:
                    self.width = w
            elif h is not None:
                self.height = h
        elif color is not None:
            if w is None or h is None: raise Exception("must supply size of color box")
            self.__dict__['_key'] = None
            self.__dict__['_image'] = None
            self.__dict__['_w'] = w
            self.__dict__['_h'] = h
            self.color = color
        pass

    def _set_key(self, name, flip, width, height, angle):
        width = int(width + 0.5)
        height = int(height + 0.5)
        angle = ((int(angle) % 360) + 360) % 360
        unrot = _image(name, flip, width, height)
        if width == 0 and height == 0:
            width = unrot.get_width()
            height = unrot.get_height()
        self.__dict__['_key'] = (name, flip, width, height, angle)
        self.__dict__['_image'] = _image(*self.__dict__['_key'])
        self.__dict__['_color'] = None
        self.__dict__['_w'] = self.__dict__['_image'].get_width()
        self.__dict__['_h'] = self.__dict__['_image'].get_height()

    def __getattr__(self, name):
        x, y, w, h = self.x, self.y, self._w, self._h
        if name == 'xspeed': name = 'speedx'
        if name == 'yspeed': name = 'speedy'
        if name == 'left': return x - w / 2
        if name == 'right': return x + w / 2
        if name == 'top': return y - h / 2
        if name == 'bottom': return y + h / 2
        if name == 'center': return x, y
        if name == 'topleft': return x - w / 2, y - h / 2
        if name == 'topright': return x + w / 2, y - h / 2
        if name == 'bottomleft': return x - w / 2, y + h / 2
        if name == 'bottomright': return x + w / 2, y + h / 2
        if name == 'width': return w
        if name == 'height': return h
        if name == 'width': return w
        if name == 'height': return h
        if name == 'size': return w, h
        if name == 'speed': return self.speedx, self.speedy
        if name == 'rect': return pygame.Rect(self.topleft, self.size)
        if name == 'image': return self.__dict__['_image']
        if name in self.__dict__:
            return self.__dict__[name]
        raise Exception("There is no '" + name + "' in a SpriteBox object")

    def __setattr__(self, name, value):
        w, h = self._w, self._h
        if name == 'xspeed': name = 'speedx'
        if name == 'yspeed': name = 'speedy'
        if name in self.__dict__:
            self.__dict__[name] = value
        elif name == 'left':
            self.x = value + w / 2
        elif name == 'right':
            self.x = value - w / 2
        elif name == 'top':
            self.y = value + h / 2
        elif name == 'bottom':
            self.y = value - h / 2
        elif name == 'center':
            self.x, self.y = value[0], value[1]
        elif name == 'topleft':
            self.x, self.y = value[0] + w / 2, value[1] + h / 2
        elif name == 'topright':
            self.x, self.y = value[0] - w / 2, value[1] + h / 2
        elif name == 'bottomleft':
            self.x, self.y = value[0] + w / 2, value[1] - h / 2
        elif name == 'bottomright':
            self.x, self.y = value[0] - w / 2, value[1] - h / 2
        elif name == 'width':
            self.scale_by(value / w)
        elif name == 'height':
            self.scale_by(value / h)
        elif name == 'size':
            if self.__dict__['_image'] is not None:
                key = self.__dict__['_key']
                self._set_key(key[0], key[1], value[0], value[1], key[4])
            else:
                self.__dict__['_w'] = value[0]
                self.__dict__['_h'] = value[1]
        elif name == 'speed':
            self.speedx, self.speedy = value[0], value[1]
        elif name == 'color':
            self.__dict__['_image'] = None
            self.__dict__['_key'] = None
            if type(value) is str: value = pygame.Color(value)
            self.__dict__['_color'] = value
        elif name == 'image':
            self.__dict__['_color'] = None
            if self.__dict__['_key'] is None:
                self._set_key(value, False, w, h, 0)
            else:
                key = self.__dict__['_key']
                self._set_key(value, *key[1:])
        else:
            sys.stderr.write("creating filed named " + name)
            self.__dict__[name] = value

    def overlap(self, other, padding=0, padding2=None):
        '''b1.overlap(b1) returns a list of 2 values such that self.move(result) will cause them to not overlap
        Returns [0,0] if there is no overlap (i.e., if b1.touches(b2) returns False
        b1.overlap(b2, 5) adds a 5-pixel padding to b1 before computing the overlap
        b1.overlap(b2, 5, 10) adds a 5-pixel padding in x and a 10-pixel padding in y before computing the overlap'''
        if padding2 is None: padding2 = padding
        l = other.left - self.right - padding
        r = self.left - other.right - padding
        t = other.top - self.bottom - padding2
        b = self.top - other.bottom - padding2
        m = max(l, r, t, b)
        if m >= 0:
            return [0, 0]
        elif m == l:
            return [l, 0]
        elif m == r:
            return [-r, 0]
        elif m == t:
            return [0, t]
        else:
            return [0, -b]

    def touches(self, other, padding=0, padding2=None):
        '''b1.touches(b1) returns True if the two SpriteBoxes overlap, False if they do not
        b1.touches(b2, 5) adds a 5-pixel padding to b1 before computing the touch
        b1.touches(b2, 5, 10) adds a 5-pixel padding in x and a 10-pixel padding in y before computing the touch'''
        if padding2 is None: padding2 = padding
        l = other.left - self.right - padding
        r = self.left - other.right - padding
        t = other.top - self.bottom - padding2
        b = self.top - other.bottom - padding2
        return max(l, r, t, b) <= 0

    def bottom_touches(self, other, padding=0, padding2=None):
        '''b1.bottom_touches(b2) returns True if both b1.touches(b2) and b1's bottom edge is the one causing the overlap.'''
        if padding2 is None: padding2 = padding
        return self.overlap(other, padding + 1, padding2 + 1)[1] < 0

    def top_touches(self, other, padding=0, padding2=None):
        '''b1.top_touches(b2) returns True if both b1.touches(b2) and b1's top edge is the one causing the overlap.'''
        if padding2 is None: padding2 = padding
        return self.overlap(other, padding + 1, padding2 + 1)[1] > 0

    def left_touches(self, other, padding=0, padding2=None):
        '''b1.left_touches(b2) returns True if both b1.touches(b2) and b1's left edge is the one causing the overlap.'''
        if padding2 is None: padding2 = padding
        return self.overlap(other, padding + 1, padding2 + 1)[0] > 0

    def right_touches(self, other, padding=0, padding2=None):
        '''b1.right_touches(b2) returns True if both b1.touches(b2) and b1's right edge is the one causing the overlap.'''
        if padding2 is None: padding2 = padding
        return self.overlap(other, padding + 1, padding2 + 1)[0] < 0

    def contains(self, x, y=None):
        '''checks if the given point is inside this SpriteBox's bounds or not'''
        if y is None: x, y = x
        return abs(x - self.x) * 2 < self._w and abs(y - self.y) * 2 < self._h

    def move_to_stop_overlapping(self, other, padding=0, padding2=None):
        '''b1.move_to_stop_overlapping(b2) makes the minimal change to b1's position necessary so that they no longer overlap'''
        o = self.overlap(other, padding, padding2)
        if o != [0, 0]:
            self.move(o)
            if o[0] * self.speedx < 0: self.speedx = 0
            if o[1] * self.speedy < 0: self.speedy = 0

    def move_both_to_stop_overlapping(self, other, padding=0, padding2=None):
        '''b1.move_both_to_stop_overlapping(b2) changes both b1 and b2's positions so that they no longer overlap'''
        o = self.overlap(other, padding, padding2)
        if o != [0, 0]:
            self.move(o[0] / 2, o[1] / 2)
            other.move(-o[0] / 2, -o[1] / 2)
            if o[0] != 0:
                self.speedx = (self.speedx + other.speedx) / 2
                other.speedx = self.speedx
            if o[1] != 0:
                self.speedy = (self.speedy + other.speedy) / 2
                other.speedy = self.speedy

    def move(self, x, y=None):
        '''change position by the given amount in x and y. If only x given, assumed to be a point [x,y]'''
        if y is None: x, y = x
        self.x += x
        self.y += y

    def move_speed(self):
        '''change position by the current speed field of the SpriteBox object'''
        self.move(self.speedx, self.speedy)

    def full_size(self):
        '''change size of this SpriteBox to be the original size of the source image'''
        if self.__dict__['_key'] is None: return
        key = self.__dict__['_key']
        self._set_key(key[0], key[1], 0, 0, key[4])

    def __repr__(self):
        return str(self)

    def __str__(self):
        return '%dx%d SpriteBox centered at %d,%d' % (self._w, self._h, self.x, self.y)

    def copy_at(self, newx, newy):
        '''Make a new SpriteBox just like this one but at the given location instead of here'''
        return SpriteBox(newx, newy, self._image, self._color, self._w, self._h)

    def copy(self):
        '''Make a new SpriteBox just like this one and in the same location'''
        return self.copy_at(self.x, self.y)

    def scale_by(self, multiplier):
        '''Change the size of this SpriteBox by the given factor
        b1.scale_by(1) does nothing; b1.scale_by(0.4) makes b1 40% of its original width and height.'''
        if self.__dict__['_key'] is None:
            self._w *= multiplier
            self._h *= multiplier
        else:
            key = self.__dict__['_key']
            self._set_key(key[0], key[1], key[2] * multiplier, key[3] * multiplier, key[4])

    def draw(self, surface):
        '''b1.draw(camera) is the same as saying camera.draw(b1)
        b1.draw(image) draws a copy of b1 on the image proivided'''
        if isinstance(surface, Camera):
            if self.__dict__['_color'] is not None:
                region = self.rect.move(-surface._x, -surface._y)
                region = region.clip(surface._surface.get_rect())
                surface._surface.fill(self._color, region)
            elif self.__dict__['_image'] is not None:
                surface._surface.blit(self._image, [self.left - surface._x, self.top - surface._y])
        else:
            if self.__dict__['_color'] is not None:
                surface.fill(self._color, self.rect)
            elif self.__dict__['_image'] is not None:
                surface.blit(self._image, self.topleft)

    def flip(self):
        '''mirrors the SpriteBox left-to-right.
        Mirroring top-to-bottom can be accomplished by
            b1.rotate(180)
            b1.flip()'''
        if self.__dict__['_key'] is None: return
        key = self.__dict__['_key']
        self._set_key(key[0], not key[1], *key[2:])

    def rotate(self, angle):
        '''Rotates the SpriteBox by the given angle (in degrees).'''
        if self.__dict__['_key'] is None: return
        key = self.__dict__['_key']
        self._set_key(key[0], key[1], key[2], key[3], key[4] + angle)


_timeron = False
_timerfps = 0


def timer_loop(fps, callback):
    '''Requests that pygame call the provided function fps times a second
    fps: a number between 1 and 60
    callback: a function that accepts a set of keys pressed since the last tick
    ----
    seconds = 0
    def tick(keys):
        seconds += 1/30
        if pygame.K_DOWN in keys:
            print 'down arrow pressed'
        if not keys:
            print 'no keys were pressed since the last tick'
        camera.draw(box)
        camera.display()

    gamebox.timer_loop(30, tick)
    ----'''
    global _timeron, _timerfps
    keys = set([])
    if fps > 1000: fps = 1000
    _timerfps = fps
    _timeron = True
    pygame.time.set_timer(pygame.USEREVENT, int(1000 / fps))
    while True:
        event = pygame.event.wait()
        if event.type == pygame.QUIT: break
        if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: break
        if event.type == pygame.KEYDOWN:
            keys.add(event.key)
        if event.type == pygame.KEYUP and event.key in keys:
            keys.remove(event.key)
        if event.type == pygame.USEREVENT:
            pygame.event.clear(pygame.USEREVENT)
            callback(keys)
        if event.type == pygame.MOUSEBUTTONDOWN:
            keys.add(event.type)
            callback(keys)

    pygame.time.set_timer(pygame.USEREVENT, 0)
    _timeron = False


def pause():
    '''Pauses the timer; an error if there is no timer to pause'''
    if not _timeron: raise Exception("Cannot pause a timer before calling timer_loop(fps, callback)")
    pygame.time.set_timer(pygame.USEREVENT, 0)


def unpause():
    '''Unpauses the timer; an error if there is no timer to unpause'''
    if not _timeron: raise Exception("Cannot pause a timer before calling timer_loop(fps, callback)")
    pygame.time.set_timer(pygame.USEREVENT, int(1000 / _timerfps))


def stop_loop():
    '''Completely quits one timer_loop or keys_loop, usually ending the program'''
    pygame.event.post(pygame.event.Event(pygame.QUIT))


def keys_loop(callback):
    '''Requests that pygame call the provided function each time a key is pressed
    callback: a function that accepts the key pressed
    ----
    def onPress(key):
        if pygame.K_DOWN == key:
            print 'down arrow pressed'
        if pygame.K_a in keys:
            print 'A key pressed'
        camera.draw(box)
        camera.display()

    gamebox.keys_loop(onPress)
    ----'''
    while True:
        event = pygame.event.wait()
        if event.type == pygame.QUIT: break
        if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: break
        if event.type == pygame.KEYDOWN:
            callback(event.key)


if __name__ == "__main__":
    camera = Camera(400, 400)

    camera.x = 10

    b = from_text(40, 50, "Blue", "Arial", 40, "red", italic=True, bold=True)
    b.speedx = 3
    b.left += 2
    b.y = 100
    b.move_speed()

    camera.draw(b)
    camera.display()

    smurfs = load_sprite_sheet("http://www.flashpulse.com/moho/smurf_sprite.PNG", 4, 4)


    def tick(keys):
        if keys:
            if pygame.K_0 in keys:
                b.image = smurfs[0]
            elif pygame.K_1 in keys:
                b.image = smurfs[1]
            elif pygame.K_2 in keys:
                b.image = smurfs[2]
            elif pygame.K_3 in keys:
                b.image = smurfs[3]
            elif pygame.K_4 in keys:
                b.image = smurfs[4]
            elif pygame.K_5 in keys:
                b.image = smurfs[5]
            elif pygame.K_6 in keys:
                b.image = smurfs[6]
            elif pygame.K_7 in keys:
                b.image = smurfs[7]
            elif pygame.K_8 in keys:
                b.image = smurfs[8]
            elif pygame.K_9 in keys:
                b.image = smurfs[9]
            elif pygame.K_a in keys:
                stop_loop()
            elif keys:
                b.image = "http://www.pygame.org/docs/_static/pygame_tiny.png"
            b.full_size()
        b.rotate(-5)
        b.center = camera.mouse
        b.bottom = camera.bottom
        camera.draw(b)
        camera.display()


    timer_loop(30, tick)

04-05 09:46