Python实用案例编程入门:第十一章 做一个年会抽奖程序

本章的主题为做一个年会抽奖程序。每个公司企业都会在每年年终的时候开年会,而年会中最值得大家期待的莫过于抽奖环节了。当然我们的抽奖形式各式各样,那么我们能否用Python写一个程序来进行抽奖呢?是不是挺有意思?那接下来我们将一起学习如何用Python写一个抽奖程序,学以致用。

11.1 要解决什么问题

这里我们要解决的问题就是用程序来模拟人的随机抽奖行为,列如,我们最常见的形式就是每人一个纸条,在上面写上自己的名字。然后投进一个大箱子里,摇一摇,领导随机抓取幸运者。那我们能否让程序来随机找出幸运者呢?也就是说我们的程序需要从所有员工中随机的找出一位幸运者。第一,需要将所有的员工信息,列如工号或名字收集起来,这就好比所有纸条放入一个大箱子里。在我们的程序里,这个大箱子可能是内存?或者文本文件,也可能是excel表等等,还可以是数据库。那接下来要解决的问题就是通过随机函数找出其中某一个幸运者,并且我们需要把已经中了奖的幸运者从名单里删除,否则他(她)还有可能继续会中奖。这就相当于把写有名字的纸条从箱子里拿了出来,那就意味着此人再也不可能中后面的奖项了。清楚了我们要解决的问题后,我们开始来看看如何解决这个问题。

11.2 实现思路

信任我们许多人都看到过类似界面,一个大屏幕上,一个个名字快速的滚动闪过。当我们让其停止的时候,程序就立刻停住,然后一个幸运的名字就稳稳的显示在那里一动不动。

言归正传,我们这里的思路是在屏幕上不断的滚动名字,当我们点击鼠标的时候就立刻停止滚动。再次点击鼠标,将继续滚动。而我们的所有名字都保存在Excel表中,所以需要从Excel表中解析出名单。

在这里,我们将鼠标的图标显示为一个鼓槌,屏幕的背景图片上有一个鼓。而这个图片上只有鼓面的范围内可以响应鼠标的点击操作,其他部分鼠标点击了没反应。

并且在我们的设想中,当鼠标点击后,名字开始滚动时,伴随着鼓声响起。点击停止后,背景音乐同时停止。

当开始下一次的随机之前,将上一次中奖的名字从名单中删除。

11.3 相关模块的安装及介绍

本节将会介绍一些使用到的模块,包括如何安装以及基本使用。这里将会涉及sys模块,xlrd模块,pygame模块和random模块。接下来,我们将逐个进行认识。

11.3.1 sys模块

sys模块是一个内建模块,不需要单独安装。

sys模块提供了对Python解释器使用的一些变量的访问,并可以进行一些修改,例如对环境变量PATH的读取和修改,并提供了某些和解释器进行交互的函数以使我们的程序能够和解释器进行交互。

例如,sys.argv会将命令行参数以list的形式传递给Python脚本,sys.argv[0]是脚本的名字,sys.argv[1]是第一个参数,以此类推。

sys.exit()表明退出程序,也可以带参数表明退出码,如果有其他程序调用该程序,即可以通过返回的数字来确定被调用程序的退出缘由。

sys.implementation查看当前正在运行的Python解释器的版本信息。

>>> sys.implementation
namespace(cache_tag='cpython-36', hexversion=50726384, name='cpython', version=sys.version_info(major=3, minor=6, micro=5, releaselevel='final', serial=0))
>>>

sys.stdin,sys.stddout,sys.stderr解释器用于标准输入,标准输出和错误。

11.3.2 xlrd模块

xlrd模式是一个专门用于解析excel文件的模块,能够从Excel文件中读取数据,无论是.xls还是.xlsx的Excel文件,都能够支持。

xlrd模块的安装命令如下:

pip install xlrd

使用比较简单,需要先打开Exce文件,然后可以得到sheet表,再基于行列得到具体cell里面的值。这里用一个简单的示例代码来进行说明,代码如下。

from xlrd import open_workbook
wb = open_workbook('simple.xls')
for s in wb.sheets():
	print('Sheet: ' + s.name)
for row in range(s.nrows):
	values = []
for col in range(s.ncols):
	values.append(s.cell(row,col).value)
	print(','.join(values))
print('
')

您可以基于这个代码进行学习,试着自己找一个Excel文件,然后用其名字替换代码中的”simple.xls”即可。记得将该Excel文件和代码文件放在同一个目录下,否则您需要使用完整绝对路径。

更多详细信息可以通过下面的链接进行学习了解。

11.3.3 pygame模块

pygame模块,是一个可以跨平台的模块,其设计目的就是为电子游戏而设计,能够支持图片和声音。它的实现使用我们开发程序时无需关注底层实现,不必被底层语言而束缚,同时也能够很快的开发出我们想要的游戏。既然是为游戏而设计的,那么用它来实现我们想要的游戏效果应该是绰绰有余。

pygame模块的安装命令如下:

PS F:5_Github> pip install pygame
Collecting pygame
Downloading https://files.pythonhosted.org/packages/11/4f/2bbfdb52c76d7d5b2a04c90e24bbb4b6970af66f0ebfe50bb32b67b51b39/pygame-1.9.6-cp36-cp36m-win32.whl (4.0MB)
|████████████████████████████████| 4.0MB 24kB/s
Installing collected packages: pygame
Successfully installed pygame-1.9.6
WARNING: You are using pip version 19.2.2, however version 19.2.3 is available.
You should consider upgrading via the 'python -m pip install --upgrade pip' command.
PS F:5_Github >

pygame模块有许多子模块,这里只对几个比较常用的模块做个简单的介绍如表11-1所示,具体需要使用某个模块的时我们可以进行深入的学习。

表11-1 pygame的模块简介

更多详细内容可以通过下面的链接进行学习。

11.3.4 random模块

random模块实现了一个伪随机数生成器。也可以用于随机选取给定范围内的数值。

random.randint(a, b) 返回一个a和b之间的随机数。

random.choice(seq) 从一个非空的seq中随机的返回一个元素。

>>> random.random()
0.30662085583434484
>>> random.uniform(2.5, 10.0)
9.727588546182595
>>> random.randrange(10)
4
>>> random.choice(['win', 'lose', 'draw'])
'win'
>>>

更多关于random模块的详细信息可以参考下面的链接。

11.4 代码实现

接下来我们看具体实现,在前面的实现思路中,我们已经大致有了一个要实现的想法。基于这个想法,我们需要做些准备。

  • 我们需要准备一张图片,这张图片用作整个界面的背景;
  • 还需要准备一段背景音乐,列如鼓声;
  • 这里还需要一个鼓槌的图片,用来替换鼠标的图形,在我们界面中鼠标将被显示为该鼓槌;
  • 保存有名字的Excel文件,设计为两列,第一列工号,第二列名字;由于实际情况中会有重名的情况,所以需要工号和名字一起显示;
  • 需要准备支持中文的字体文件,列如simhei.ttf,默认字体只支持英文

这里我们假设已经万事俱备只欠编码,那么接下来我们就是开始具体实现。

11.4.1 编写伪码

按照惯例,我们先来编写伪码。

#从Excel文件中得到工号和名字信息,存入list中
#利用pygame初始化显示界面,例如全屏模式或特定大小
#设置屏幕窗口的标题

#初始化音频,加载背景音乐
#利用随机函数在名单list中随机选取一个名字

while True:
#利用pygame.event.get()得到事件
if ESPACE键被按下,则退出程序
elif MOUSE鼠标键被按下,则进行处理

#pygame.mouse.get_pos()获取鼠标的位置
#pygame.mouse.set_visible(False)设置鼠标不可见,由于需要用鼓槌来取代鼠标图标

if not pause_flag:
    #如果不是暂停状态,需要从名字list中随机选出一个名字
    #并且pygame.mixer.music.play()播放背景音乐

#用工号和名字构造字符串,用于在屏幕上特定区域显示

#font = pygame.font.Font("simhei.ttf", 30) 由于需要显示中文,所以这里用支持中文的字体文件

pygame.display.update() #更新屏幕显示

11.4.2 Python代码

代码的细节,会通过注释进行解释其作用。

#coding:utf-8

#导入我们需要使用到的库
import sys
import xlrd
import pygame
import random
from pygame.locals import *

fullScreen = True
mode = 0
#mouse_scan用于确定响应鼠标点击的鼓面的范围坐标
mouse_scan = [(1031, 498, 116, 89), (1031, 436, 91, 68)]
#pos_scan用于确定显示界面上显示名字的坐标范围
pos_scan = [(683, 141, 683,630), (650, 130, 650, 540)]


def get_name_list_from_excel(file_name):
    '''"解析 人员.xlsx 文件,得到人员名单列表"'''
    name_list = []
    excelFile = xlrd.open_workbook(file_name)
    sheet = excelFile.sheet_by_name('Sheet1')
    print sheet.name, sheet.nrows, sheet.ncols
    job_num  = sheet.cell(0, 0).value.encode('utf-8')
    job_name = sheet.cell(0, 1).value.encode('utf-8')
    for row in range(1, sheet.nrows):
        job_num  = sheet.cell(row, 0).value.encode('utf-8') #’utf-8’用于中文编码
        job_name = sheet.cell(row, 1).value.encode('utf-8')
        #print job_num, job_name
        name_list.append((job_num, job_name)) #将工号和名字组成的tuple加入list
    return job_num, job_name, name_list


def handle_mouse_event(index, pause_flag):
    #处理鼠标事件
if pygame.mouse.get_pressed()[0]:
    #如果鼠标被按下了,就获取鼠标的位置
        x, y = pygame.mouse.get_pos()
        x -= m.get_width() / 2
        y -= m.get_height() / 2
        if mouse_scan[mode][0] + mouse_scan[mode][2] > x > mouse_scan[mode][0] and mouse_scan[mode][1] + mouse_scan[mode][3] > y > mouse_scan[mode][1]:
            pause_flag = True if pause_flag == False else False
            if not pause_flag:
                #如果不是在暂停,就开始播放音乐
                pygame.mixer.music.play()
                del name_list[index] #将之前选中的名字从list中删除
                #print len(name_list)
                #show_name_list(name_list)
            else:
                pygame.mixer.music.stop() #如果是在暂停就停止播放音乐

    return index, pause_flag


def show_name_list(name_list):
    '''调试接口,输出当前名单'''
    for index in range(0, len(name_list)):
        str = "%s %s" % (name_list[index][0], name_list[index][1])
        print(str.decode('utf-8'))

if __name__ == "__main__":
    job_num, job_name, name_list = get_name_list_from_excel(r'name_file.xlsx')
    print len(name_list)

    pygame.init()
    bg = 'bg.png'
    mg = 'gc_cz.png'

    if fullScreen: #设置全屏模式
        mode = 0
        bg = 'bg_1366x768.png'
        screen = pygame.display.set_mode((1366, 768), FULLSCREEN, 32)
    else:
        mode = 1
        bg = 'bg.png'
        screen = pygame.display.set_mode((1340, 670), 0, 32)

#设置窗口标题
    pygame.display.set_caption("Annual meeting lottery")

    b = pygame.image.load(bg).convert() #装载背景图片
    m = pygame.image.load(mg).convert_alpha()
    screen.blit(b, (0, 0))
    screen.blit(m, (0, 0))

    pygame.mixer.init()
    pygame.mixer.music.load('9224.wav') #装载背景音乐
    #pygame.mixer.music.set_volume(0.5) #设置声音的大小
    pygame.mixer.music.play() #开始播放声音

    index = random.randint(0, len(name_list) - 1) #得到名单大小范围内的随机数。
    pause_flag = False
    while True:
        for event in pygame.event.get():
            if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
                pygame.quit()
                sys.exit(0)
            elif event.type == MOUSEBUTTONDOWN:
                index, pause_flag = handle_mouse_event(index, pause_flag)

        screen.blit(b, (0, 0))
        x, y = pygame.mouse.get_pos() #得到鼠标的位置
        x -= m.get_width() / 2
        y -= m.get_height() / 2
        pygame.mouse.set_visible(False) #设置鼠标不可见
        screen.blit(m, (x, y))

        if not pause_flag:
            index = random.randint(0, len(name_list)-1)
            if not pygame.mixer.music.get_busy():
                pygame.mixer.music.play()

        text_context = '%s %s' % (name_list[index][0], name_list[index][1])
        #print text_context
        font = pygame.font.Font("simhei.ttf", 80) #设置字体支持显示中文
        text_obj = font.render(text_context.decode('utf-8'), True, (255, 255, 255), (0, 0, 0))
        text_pos = text_obj.get_rect()
        text_pos.center = (pos_scan[mode][0], pos_scan[mode][1])
        screen.blit(text_obj, text_pos) #刷新文本内容

        font = pygame.font.Font("simhei.ttf", 30)
        text_obj = font.render('祝公司蓬勃发展,祝同事万事如意'.decode('utf-8'), True, (255, 255, 255), (255, 0, 0))
        text_pos = text_obj.get_rect()
        text_pos.center = (pos_scan[mode][2], pos_scan[mode][3])
        screen.blit(text_obj, text_pos)

        pygame.display.update() #更新屏幕显示

最后我们需要将Python代码打包为exe,方便于在windows上运行,下节介绍如何打包为exe文件进发布。

11.4.3 打包为exe安装文件进行发布

我们的程序编写好了,也运行良好,能解决我们的问题。目前如果想把它分享给我们亲爱的其他同事,我们该怎么做呢?最好是将它打包为EXE文件,否则使用的人也需要安装Python,且必须安装所有的依赖库,似乎太麻烦了,没等听您解释完就不想用了。因此,我们需要将Python代码进行打包,生成exe进行方便使用。

这里我们同时也提供了打包为EXE的代码。由于我们使用pyinstaller进行打包,所以需要提前安装。当然安装也很简单,使用pip命令就可以安装。

pip install pyinstaller

安装成功后我们就可以通过下面的代码进行打包了。

接下来,将Python代码转换为exe程序,我们使用的是pyinstaller,具体的转换代码我实现为python代码。只需要执行下面的Python代码,就会调用pyinstaller生成一个单独的exe文件。这种形式的优点是只有一个exe文件,其他所有的依赖的Python环境文件都被打包在该exe文件中。而缺点就是由此导致该文件比较大,而且每次执行都相当于有一个解压到临时目录的过程,所以执行比较慢。

import os, shutil
from subprocess import Popen

if os.path.exists('dist'):
	shutil.rmtree("dist")
if os.path.exists('build'):
	shutil.rmtree("build")
if os.path.exists('__pycache__'):
	shutil.rmtree("__pycache__")

handle = Popen("pyinstaller -F -w --add-data res;res -i res/bit.ico Annual_awards_main.py")
handle.wait()
shutil.copyfile("dist/Annual_awards_main.exe", "./Annual_awards_main.exe")
shutil.rmtree("dist")
shutil.rmtree("build")
shutil.rmtree("__pycache__")

个人推荐将其打包为目录,而不是单个exe文件。这样的好处就是不需要每次执行都进行解压到临时目录,而是直接在同目录下调用执行,因此比较快。下面就是将Python文件打包为目录的代码,跟上面有区别,其中一个打包参数为-D。下面的脚本生成的目录dist中,会有相应的exe文件,可以直接双击运行。

import os, shutil
from subprocess import Popen

if os.path.exists('dist'):
	shutil.rmtree("dist")
if os.path.exists('build'):
	shutil.rmtree("build")
if os.path.exists('__pycache__'):
	shutil.rmtree("__pycache__")

handle = Popen("pyinstaller -D -w --add-data res;res -i res/bit.ico Annual_awards_main.py")
handle.wait()

而为了更方便,我们这里会介绍如何将该目录打包为安装文件,这样在给别人用的时候,只需要给一个安装文件。用户拿到该安装文件后,安装上之后就可以使用了,且使用感受更好。我们这里介绍一个将目录打包为安装文件的工具程序。该工具程序名为Inno Setup,是一个免费的工具软件,网站主页如下图11-1所示。可以点击Download Inno Setup链接进行下载,详细信息可以登陆网站()进行了解。

Python实用案例编程入门:第十一章 做一个年会抽奖程序

图11-1 Inno Setup主页

这里,我们可以按照Inno Setup的引导来创建普通的程序安装文件,创建完成后会生成内容如下所示的iss文件。当然,如果我们提前有该文件的话,可以直接用Inno Setup程序直接打开该文件,执行编译来生成安装文件。

; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!

#define MyAppName "Annual_awards_main"
#define MyAppVersion "2.2"
#define MyAppPublisher "ggang.liu, Inc."
#define MyAppExeName "Annual_awards_main.exe"

[Setup]
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{F3D8DE4F-EA0D-47E7-AFBC-7FE7A8EAD8D3}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
DefaultDirName={autopf}{#MyAppName}
DisableProgramGroupPage=yes
; The [Icons] "quicklaunchicon" entry uses {userappdata} but its [Tasks] entry has a proper IsAdminInstallMode Check.
UsedUserAreasWarning=no
InfoAfterFile=C:Annual_awards_main
eleaseREADME.md
; Remove the following line to run in administrative install mode (install for all users.)
PrivilegesRequired=lowest
PrivilegesRequiredOverridesAllowed=dialog
OutputDir=C:Annual_awards_main
elease
OutputBaseFilename=Annual_awards_main-setup
SetupIconFile=C:Annual_awards_main
est.ico
Password=dot
Compression=lzma
SolidCompression=yes
WizardStyle=modern

[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"

[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 6.1; Check: not IsAdminInstallMode

[Files]
Source: "C:Annual_awards_maindistAnnual_awards_mainAnnual_awards_main.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "C:Annual_awards_maindistAnnual_awards_main*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files

[Icons]
Name: "{autoprograms}{#MyAppName}"; Filename: "{app}{#MyAppExeName}"
Name: "{autodesktop}{#MyAppName}"; Filename: "{app}{#MyAppExeName}"; Tasks: desktopicon
Name: "{userappdata}MicrosoftInternet ExplorerQuick Launch{#MyAppName}"; Filename: "{app}{#MyAppExeName}"; Tasks: quicklaunchicon

[Run]
Filename: "{app}{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent

这里会打包生成的安装文件是一个exe文件,我们双击执行安装时会出现类似图11-2所示的安装界面,可以看出是一个很常见的程序安装界面,跟其他程序安装没有什么区别,看起来更专业。

Python实用案例编程入门:第十一章 做一个年会抽奖程序

图11-2 服务程序安装界面

到这里,程序的打包安装就介绍完毕,具体选择哪种方式进行打包,可以根据具体情况而定。试着为自己的程序进行打包并安装,让我们的小工具的安装使用足够专业和易用。

11.5 本章小结

本章我们介绍了几个有用模块,列如sys模块,用于读取Excel文件的xlrd模块,以及支持编写游戏的pygame模块。后面我们还将再次使用该模块去实现一个拼图游戏,将会学到更多关于这方面的内容。

通过这些模块我们很容易的实现了一个抽奖程序,当然也不仅仅可以用于抽奖。如果您是老师,也可以将其用于课堂随机点名等等其他类似的场景。

最后我们也介绍了如何将我们Python代码打包为exe文件,方便于在没有安装Python及相关以来库的Windows电脑上运行,就像一个普通的exe程序一样。


欢迎关注,转发,收藏,感谢感谢


Python实用案例编程入门:第一章 Python概述及为什么学Python

Python实用案例编程入门:第二章 字符串

Python实用案例编程入门:第四章 字典和文件

Python实用案例编程入门:第六章 控制流语句

Python实用案例编程入门:第五章 函数和类

Python实用案例编程入门:第三章 列表和元组

Python实用案例编程入门:第七章 调式手段

Python实用案例编程入门:第十章 用Python处理音频文件

Python实用案例编程入门:第九章 爬虫下载VOA每日广播英语MP3

Python实用案例编程入门:第八章 如何自动连接WIFI

© 版权声明

相关文章

6 条评论

  • 头像
    读者

    比如上课点名

    无记录
    回复
  • 头像
    杜仲 读者

    童年阴影系列软件?上课点名,随机提问。。。

    无记录
    回复
  • 头像
    巴蜀茗人 读者

    童年阴影系列,这个创意可以

    无记录
    回复
  • 头像
    薇涅拉 读者

    #玉田吴云飞# 实用案例

    无记录
    回复
  • 头像
    明晓哥 读者

    收藏了,感谢分享

    无记录
    回复
  • 头像
    薛冰雷单单计电票通 读者

    炒鸡实用的案例,稍微改改就可以变成专家库抽取或其他的实用小程序。

    无记录
    回复