编程娃娃Python版

"""bcwawa.py

   《编程娃娃Python版》是继《编程娃娃》的姊妹篇。它不需要拖曳编程命令,采用的是直接编排图形化指令的模式,指挥一个娃娃回家。

   此版本更重要的是能做为一个模块导入到Python原生环境,从而实现用真正的Python命令去指挥娃娃移动。

   本版本新增命令如下:

   forward:前进50像素,缩写为:fd,使用方法: fd()

   backward:倒退50,缩写为:bk,back,使用方法:bk()

   right:右转90,缩写:rt, 使用方法:rt()

   left:左转90,缩写:lt,使用方法:lt()

   jump:前进100,主要用来跳过炸弹。使用方法:jump()

   bomb_ahead:检测娃娃前面是否有炸弹,返回布尔值。使用方法: bomb_ahead()

   playmusic:播放音乐,不输入参数会播放小星星曲子。使用方法: playmusic()

   pausemusic:暂停播放音乐。使用方法: playmusic()

   stopmusic:停止播放音乐。使用方法: stopmusic()

   unpausemusic:继续播放音乐。使用方法: unpausemusic()

   set_level:设置当前关卡号。使用方法: set_level(3)

   娃娃根据指令前进的主要原理如下:
   在作品中设计了三排按钮,娃娃能访问这三排按钮。
   第一排是子指令表1(相当于子程序1),以橙星开始,后面跟着9个指令按钮。
   第二排也是子指令表2(相当于子程序2),以绿星开始,后面跟着9个指令按钮。
   第三排代表主指令表(相当于主程序),以信封开始,后面跟着9个指令按钮.
   程序启动后,娃娃对象生成,它会注册回车键绑定自己的execute方法。
   当按回车键或单击信封按钮时,娃娃就像是收到了信的内容,会根据信的内容执行它的execute方法。
   这个方法执行时会遍历主指令列表,从而根据主指令表中的命令让娃娃前进,转向或执行子指令表。
   子程序也能调用子程序,但不能调用主程序。当子程序递归调用过多时,娃娃会自动停止移动。
 
   让娃娃移动的三种方法:
   
   1、按键:直接按上、下、左、右,空格键,手动操作娃娃。适合于体验按键。

   2、编排图形化指令:单击信封及五角星后面的按钮,给娃娃编排指令。信封后面的9个按钮为主指令表,橙星为调用子程序1指令,绿星为调用子程序2指令。   

      向上箭头为前进指令(并不仅仅是向上移动,而是朝它当前的方向移动)。向右转箭头为右转指令,向左转箭头为左转指令,弯箭头为跳跃指令。

   3、Python编程模式:把本程序做为模块导入,然后通过编写Python代码指挥娃娃前进。典型代码举例:

      from bcwawa import *    
      set_level(9)            # 设置当前关卡为第9关。
      sleep(3)
      for i in range(4):      # 这个for循环的意思是重复4次“前进前进右转”
          fd();fd()
          rt()

   配置文件名:cfg.txt
   本程序的房子和炸弹这两种道具摆放方式分为过关模式与随机模式。过关模式是按内置的排列顺序每关摆放在固定的位置。
   随机模式每个关卡都是随机的。
   如果配置文件不存在,则默认为过关模式。
   如果配置文件存在,里面的字符串为1,则也是关卡模式,其它字符则为随机摆放模式。
   主要原理:在生成娃娃对象时,传递__game_mode__变量的值,让娃娃知道模式,当它碰到房子就能决定采用哪种方式重新摆放道具。
   
   

"""
__author__ = "李兴球"
__date__ = "2018/10"
__email__ = "406273900@qq.com"
__version__ = "1.0"

import sys,os,webbrowser
from turtle import *
from codewawa.gridturtle import *
from codewawa.bomb import *
from codewawa.house import *
from codewawa.wawa import *
from codewawa.command import *
from random import randint
from time import sleep

"全局变量加双下划线,不污染全局名称空间"
__gamename__ = "编程娃娃Python版"
__images_folder__ = os.getcwd() + os.sep + "images" + os.sep
__audios_folder__ = os.getcwd() + os.sep + "audios" + os.sep
__explosion_sound__=__crystalsound__=__stepsound__=__rotatesound__=__jumpsound__ = None
__sounds__ = [ ]                                     # 娃娃的三个音效表
__pygame_normal__ = True                             # 描述pygame是否正常或能否初始化混音器
__wawagif__ = []                                     # 娃娃的4个方向gif图片表
__commandpic__= []                                   # 命令按钮gif图片表
__screen_width__,__screen_height__ = 660,660         # 设定屏幕宽度和高度
__explode_image_list__ = []                          # 爆炸效果造型表
__house_list__ = []                                  # 多个图片只是为了房子有动态效果
__mute_list__ = []
__all_images__ = ['invalid.gif', 'roundman_down.gif', 'roundman_left.gif', 'roundman_right.gif', 'roundman_up.gif', 'volumeOff.gif', 'volumeOn.gif', '信封.gif', '前进.gif', '右转.gif', '左转.gif', '房子0.gif', '房子1.gif', '橙星.gif', '橙色星.gif', '炸弹.gif', '爆炸效果-0.gif', '爆炸效果-1.gif', '爆炸效果-10.gif', '爆炸效果-11.gif', '爆炸效果-2.gif', '爆炸效果-3.gif', '爆炸效果-4.gif', '爆炸效果-5.gif', '爆炸效果-6.gif', '爆炸效果-7.gif', '爆炸效果-8.gif', '爆炸效果-9.gif', '绿星.gif', '绿色星.gif', '跳跃.gif']
__all_images__ = [ __images_folder__ + image for image in __all_images__]
__download_url__ = "http://www.scratch8.net/downloads/bcwawa_python.rar"

def check_images():
    """图像文件丢失检测,在主程序所在文件夹下面要有名为images目录名的文件夹,存储图像资源。"""
    for image in __all_images__:
        if not os.path.exists(image):
            info = image + "文件丢失。"
            info = info + "请从以下网址重新下载本软件:" + "\n"
            info = info  + __download_url__            
            showerror(__gamename__,info)
            
            return False
    return True


def __load_cfg__(filename):
    """加载配置文件"""
    if os.path.exists(filename):   # 配置文件存在则读取字符串
        f = open(filename)
        __game_mode__ = f.read()
        f.close()
        if __game_mode__.strip() == "1":# 字符串为1,关卡模式为过关模式
            __game_mode__ = 1
        else:
            __game_mode__ = 0           # 为其它字符串为随机摆放模式
    else:
        __game_mode__ = 1               # 文件不存在,默认为关卡模式
    return __game_mode__


    
def __init_screen__(width,height,bgcolor,title):
    """初始化屏幕,注册gif图到形状列表"""
 
    __wawagif__.append(__images_folder__ + "roundman_right.gif")  # 娃娃朝向右的图形
    __wawagif__.append(__images_folder__ + "roundman_up.gif")     # 娃娃朝向上的图形
    __wawagif__.append(__images_folder__ + "roundman_left.gif")   # 娃娃朝向左的图形
    __wawagif__.append(__images_folder__ + "roundman_down.gif")   # 娃娃朝向下的图形

    __commandpic__.append(__images_folder__ + "前进.gif")    # “前进”指令的图形
    __commandpic__.append(__images_folder__ + "左转.gif")    # “左转” 指令的图形
    __commandpic__.append(__images_folder__ + "右转.gif")    # “右转”指令的图形
    __commandpic__.append(__images_folder__ + "橙星.gif")    # “子程序1”指令的图形
    __commandpic__.append(__images_folder__ + "绿星.gif")    # “子程序2”指令的图形
    __commandpic__.append(__images_folder__ + "跳跃.gif")    # “跳跃”指令的图形
    __commandpic__.append(__images_folder__ + "invalid.gif") #  “无”指令的图形

    __explode_image_list__ .append( __images_folder__ + "炸弹.gif")                         # 定义爆炸造型表
    __explode_image_list__.extend([ __images_folder__ + "爆炸效果-" + str(i) + ".gif" for i in range(12)])

    __house_list__.append(__images_folder__ + "房子0.gif")
    __house_list__.append(__images_folder__ + "房子1.gif")

    __mute_list__.append(__images_folder__ + "volumeOn.gif")
    __mute_list__.append(__images_folder__ + "volumeOff.gif")
    
    screen = Screen()
    screen.title(title)
    screen.bgcolor(bgcolor)
    screen.setup(width,height)
    screen.delay(0)
    if not check_images():screen.bye();return
      
    [screen.addshape(image) for image in __commandpic__]  # 注册所有命令按钮的造型到屏幕
    [screen.addshape(image) for image in __wawagif__]     # 注册所有娃娃的造型到屏幕的形状列表
    [screen.addshape(image) for image in __explode_image_list__]  # 注册炸弹的所有造型到形状列表    
    [screen.addshape(image) for image in __house_list__]   # 注册房子的造型到形状列表
    [screen.addshape(image) for image in __mute_list__]   # 注册静音按钮的造型到形状列表        
    
    return screen

def __draw_grid__(start,end,rowscols):
    """画格子的函数,参数说明:
    start:起始坐标元组,左上角
    end:结束坐标元组,  右下角
    rowscols:行和列数
    """
    g = Gridturtle()                        # 画格子的海龟对象   
    g.drawgrid(start,end,rowscols,"gray",4) # 格子海龟专业画格子

def __make_button__():
    """生成命令按钮,每个按钮有7种指令,对应7张gif图,它们都在列表中。
       单击按钮会调用相应的方法切换指令,同时切换到相应的图形。

    """
    suborange = []    # 子程序1指令序列
    for x in range(-200,201,50):
        suborange.append(Command(__commandpic__,__commandlist__,x,-100))
        
    subgreen = []     # 子程序2指令序列
    for x in range(-200,201,50):
        subgreen.append(Command(__commandpic__,__commandlist__,x,-150))
        
    main = []         # 主程序指令序列
    for x in range(-200,201,50):
        main.append(Command(__commandpic__,__commandlist__,x,-200))
        
    return suborange,subgreen,main
 
         
def forward():
    """让娃娃前进50个像素"""
    wawa.fd50()
def right():
    """让娃娃右转90度"""
    wawa.rt90()
def left():
    """让娃娃左转90度"""
    wawa.lt90()
def jump():
    """让娃娃跳过一格,实际上是前进100个像素"""
    wawa.jump()
def backward():
    """让娃娃倒退50个像素"""
    wawa.fd_50()



def bomb_ahead():
    """检测在娃娃的前进方向上是否有一个炸弹"""
    return wawa.front_have_bomb_check()


def playmusic(filename = __audios_folder__ + "小星星.wav",times = -1):
    """播放音乐,times:播放次数,-1表示无限,0表示1次,1表示播放1次,2表示播放3次,详情看pygame说明书"""
    try:
        pygame.mixer.music.load( filename)
        pygame.mixer.music.play(times,0)
    except:
        print("出错了,音乐文件不存在或无法播放。")
        
    
def stopmusic():
    """停止播放音乐"""
    try:
        pygame.mixer.music.stop()
    except:
        pass   
def pausemusic():
    """暂停播放音乐"""
    try:
        pygame.mixer.music.pause()
    except:
        pass   
def unpausemusic():
    """继续播放音乐"""
    try:
        pygame.mixer.music.unpause()
    except:
        pass
    
def __init_audio__():
    """本函数试图导入pygame模块,如果导入不成功或者混音初
    始化不成功,相关变量的值都为None,程序仍旧能运行,只是不发声。
    """
    global __pygame_normal__,__explosion_sound__,__crystalsound__,__stepsound__,__rotatesound__,__jumpsound__
    try:
        import pygame
        pygame.mixer.init()  
    except:
        pygame = None
        __pygame_normal__ = False
        print("pygame模块没有安装或混音器初始化不成功。请重新安装pygame模块。\n安装方法,请在命令提示符下输入:pip install pygame --user。")

    if __pygame_normal__:                   # 如果pygame正常
        try:
            __explosion_sound__ = pygame.mixer.Sound( __audios_folder__ + "BOMB2.wav")
            __crystalsound__ = pygame.mixer.Sound( __audios_folder__ + "水晶.wav")
            __stepsound__ = pygame.mixer.Sound( __audios_folder__ + "步进声.wav")
            __rotatesound__ = pygame.mixer.Sound( __audios_folder__ + "转弯声.wav")
            __jumpsound__ = pygame.mixer.Sound( __audios_folder__ + "跳跃声.wav")
        except:
            pass         
        __sounds__.append(__stepsound__)
        __sounds__.append(__rotatesound__)
        __sounds__.append(__jumpsound__)
    return pygame

def set_level(number = 0):
    """设置起始关卡"""
    if type(number) != type(3) : number = 1
    if number <= 0 :number = 1
    wawa.level_number = number - 1
    wawa.resetbombs()
    wawa.display_info() #显示信息


"""定义别名"""
前进 = 走你 = fd = qianjin = qj = go = forward
后退 = 倒退 = bk = daotui = dt = back = backward
右转 = 向右 = rt = right
左转 = 向左 = lt = left
关卡 = 设关卡 = 设定关卡 = 设置关卡 = 设置关卡号 = setlevel = set_level
等待 = 延时  = sleep
跳 = 跳跃 = jump

pygame = __init_audio__()                  
screen = __init_screen__(__screen_width__,__screen_height__,"white",__gamename__)   # 新建屏幕对象
if screen==None:sys.exit(0)

__game_mode__ = __load_cfg__("cfg.txt")

#-------------------------画格子 ----------------------------------
"""画一个5x9的格子,每个格子的像素为50x50。
"""
__draw_grid__((-225,250),(225,0),(5,9))       # 画格子,起始坐标,结束坐标,行列数


#------------------------生成命令按钮------------------------------
"""这段程序生成三行命令按钮,每行有9个按钮,从上到下。
第一行的按钮是代码娃娃的子程序1,它用橙色星星代表。用suborange列表保存。
第二行的按钮是代码娃娃的子程序2,它用绿色星星代表。用subgreen列表保存。
第三行的按钮是代码娃娃的主程序, 它用一封信代表。用main列表保存。

"""
__commandlist__ = ["前进","左转","右转","suborange","subgreen","跳跃","pass"]
suborange,subgreen,main = __make_button__() # 生成橙,绿,主程序指列按钮    

#----------------------炸弹 ----------------------------------
"""本段程序新建12个炸弹

"""
def __manual_bomb__():
    """按z键人工生成一个炸弹,在(0,-25)坐置"""
    zd = Bomb(__explode_image_list__,__explosion_sound__)
    zd.goto(0,-25)
    zd.showturtle()
    bombs.append(zd)
    
if __game_mode__ == 1:
    bombs = [Bomb(__explode_image_list__,__explosion_sound__) for i in range(36)]   # 新建36个炸弹
else:
    bombs = [Bomb(__explode_image_list__,__explosion_sound__) for i in range(12)]   # 新建12个炸弹
screen.onkeypress(__manual_bomb__,"z") #按z键可人工生成一颗炸弹
#----------------------6、房子 ----------------------------------
"""本段程序生成房子对象,参数为房子造型列表,水晶声,炸弹列表
"""

house = House(__house_list__,__crystalsound__,bombs)                 # 新建房子对象

#----------------------娃娃 ----------------------------------
"""本段程序生成娃娃对象,它的参数如下:
  __wawagif__:它是已经注册到形状列表的娃娃的4个造型。
  main:娃娃的要执行的主程序指令表。
  suborange:娃娃的主程序将调用的子程序1。
  subgreen:娃娃的程序将调用的子程序2。
  house:娃娃可访问房子。
  __allcoordinates__:所有格子中央坐标点。洗牌后就是随机的,用于随机放置炸弹。
  bombs:娃娃可访问所有炸弹。
  __sounds__:娃娃的音效列表。     

"""
__allcoordinates__=[]                           # 所有格子的坐标中心点
__gridx__,__gridy__ = -200,25                   # 左下角格子中心点坐标
for __x__ in range(__gridx__,201,50):
    for __y__ in range(__gridy__,226,50):
        #print("(",__x__,",",__y__,")",end = ",")
        __allcoordinates__.append((__x__,__y__))
    #print()

__ps__ = __wawagif__,main,suborange,subgreen,house,__allcoordinates__,bombs,__sounds__,__game_mode__,Command
wawa =Wawa(*__ps__)                   # 新建娃对象,它的默认的坐标为(-200,25)

if __game_mode__ != 1 :               # 随机模式
    wawa.resetbombs()                 # 重置炸弹的位置
else:
    set_level()


#----------------------放几个标志----------------------------------
__flag_images__ = [ __images_folder__ + "橙色星.gif", __images_folder__ + "绿色星.gif", __images_folder__ + "信封.gif"]
[screen.addshape(image) for image in __flag_images__]
__flag__ = Turtle(visible = False)   # 初始状态是不可见的
__flag__.pencolor("orange")
__flag__.penup()
__x__,__y__ = 0,270
__flag__.goto(__x__,__y__)
__flag__.write(__gamename__,align='center',font=("黑体",16,"normal"))

__x__,__y__ = -260,-100
for __i__ in range(3):        
    __flag__.shape(__flag_images__[__i__])
    __flag__.goto(__x__,__y__)
    if __i__ < 2 : __flag__.stamp()    # 最后一个不盖章,要不然无法单击信封实现运行指令
    __y__ = __y__ - 50
    
__envelopex__ = __flag__.xcor()
__envelopey__ = __flag__.ycor()

__x__,__y__ = -230,-110

__flag__.pencolor("gray")
for __i__ in range(3):         
    __flag__.goto(__x__,__y__)
    __flag__.write("=",align='center',font=("Arial",14,"normal"))
    __y__ = __y__ - 50

__flag__.goto(__envelopex__,__envelopey__)
__flag__.showturtle()
__flag__.onclick(lambda __x__,__y__:wawa.execute())    # 单击左键执行main指令集
__flag__.onclick(lambda __x__,__y__:wawa.gohome(),3)   # 单击右键回到左下角格子

#----------------------播放与静音按钮 ----------------------------------
"静音按钮,起始状态是on,单击后放音乐。造型列表为:__mutebutton__"
__mute_index__ = 0
def __alt_music_status__():
    """播放音乐或停止播放"""
    global __mute_index__
    
    __mutebutton__.shape(__mute_list__[1 -__mute_index__])
    if __mute_index__ == 1:
        playmusic()
    else:
        stopmusic()
    __mute_index__ = 1 - __mute_index__
    
__x__,__y__ = -260,-250
__mutebutton__ = Turtle(shape = __mute_list__[0],visible = False) # 初始状态是不可见的
__mutebutton__.penup()
__mutebutton__.goto(__x__,__y__)
__mutebutton__.showturtle()
__mutebutton__.onclick(lambda x,y:__alt_music_status__())
playmusic()
screen.listen() 

if __name__ == "__main__":

     def open_url(x,y):
         #print(x,",",y)
         if x>=-328 and x<=323 and y>=-321 and y<=-270:             
             webbrowser.open("http://www.scratch8.net/wawa_python.php")
         
     copyrighter  = Turtle(visible=False)
     copyrighter.penup()
     copyrighter.color("gray")
     copyrighter.sety(-300)
     copyrighter.write("风火轮少儿编程出品,操作说明:http://www.scratch8.net/wawa_python.php",align='center',move=False,font=("楷体",12,"normal"))
     screen.onclick(open_url)     
     screen.mainloop()




    

编程娃娃Python版