章节十一:项目实操:PK小游戏(2)

章节十一:项目实操:PK小游戏(2)

这次的项目实操流程,和项目1是一模一样的:

image.png-21.2kB

接下来,我们一步一步来推进这个项目的实现吧。

1. 明确项目目标

互联网公司有两种角色,产品经理、程序员。产品经理提开发需求给程序员,然后程序员开发出满足需求功能的程序。

今天我们来做个模拟,我们扮演“程序员”接到“产品经理”需求,并完成开发的过程。

image.png-26.8kB

需求文档是这样子的:

image.png-108.9kB

看过了需求文档,产品经理又跟你嘱咐:

image.png-35.3kB

“敏捷开发”是互联网的常态。刚刚当“程序员”的第一天,你就接到了这么急的需求,那该怎么办呢?我们来分析一下。

2. 分析过程,拆解项目

我们的任务是制作一个工作量计算器,虽然产品经理的需求很急,但我们不要着急开发,应该先梳理清楚这是一个什么需求,要做出的程序(产品)功能如何。毕竟,磨刀不误砍柴工。

既然是个计算器程序,那就是要输入信息后能计算出结果。为了搞清楚计算过程,我们需要根据案例倒推出计算公式。

我们先梳理一下需求文档中的关键信息:

image.png-44.8kB

那么现在请你尝试倒推计算公式,写在下面的代码区吧:

image.png-284.2kB

我们已经搞清楚了核心计算过程,接下来我们得拆分一下阶段版本。

因为产品经理提到需求非常急,要赶快做出能用的程序,后续再迭代改良。所以,我们把程序版本大致规划成三个阶段:

image.png-23.2kB

明确了每一阶段的任务后,接下来就是好戏开场,让我们开始逐步用代码实现功能!

3. 逐步执行,代码实现

让我们先一起来攻克版本1.0:

3.1 版本1.0:能用就好

要做一个“能用就好”的最基本的程序,我们可以直接编写一个带参数函数完成计算功能。程序写出来大概长这个结构:

# 工时计算
def estimated_time(size,number):
    ……(计算过程)

# 人力计算
def estimated_number(size,time):
    ……(计算过程)

estimated_time(参数1,参数2)
estimated_number(参数1,参数2)

也就是说,把计算过程写好,封装到函数里,然后需要计算的时候直接调用函数,然后运行程序就能完成计算。

我们刚才总结的计算公式是这样的:

# 工时计算公式
time = size * 80 / number

# 人力计算公式
number = size * 80 / time

现在请你来完善一下代码(注意,在函数中不但要完成计算,还需要用print语句把计算结果展示出来哦):

完善代码的过程中,我们会用到第一个项目末尾提到的知识点:格式化字符串。

image.png-65.1kB

代码如下:

image.png-343.7kB

注:%f的意思是格式化字符串为浮点型,%.1f的意思是格式化字符串为浮点型,并保留1位小数。

# 工时计算
def estimated_time(size,number):
    time = size * 80 / number
    print('项目大小为%.1f个标准项目,使用%d个人力完成,则需要工时数量为:%.1f个' %(size,number,time))

# 人力计算
def estimated_number(size,time):
    number = size * 80 / time
    print('项目大小为%.1f个标准项目,如果需要在%.1f个工时完成,则需要人力数量为:%d人' %(size,time,number))

# 调用工时计算函数
estimated_time(1.5,2)
# 调用人力计算函数
estimated_number(0.5,20)

到这里,“工作量计算器”的最基本版本就制作完成了,你把它交付给了产品经理。

3.2 版本2.0:稍作改良

产品经理拿走了版本1的代码,过了几天,他又回来跟你反馈计算公式有点问题:

image.png-428.2kB

产品经理把运行结果展示给你看:

image.png-29.3kB

那问题来了,要怎么调整代码,才能实现产品经理的需求对人数向上取整呢?也就是计算结果是1.5人的时候,取整数2,计算结果3.8人的时候,取整数4,计算结果10.1人的时候,取整数11……

第一步定位问题:代码的计算过程应该对对人数向上取整。也就是计算结果是1.5人的时候,取整数2,计算结果3.8人的时候,取整数4,计算结果10.1人的时候,取整数11……

第二步,我们可以直接寻找新知识,在搜索引擎中搜索“python 取整”,就能找到向上取整的函数。

image.png-417.7kB

image.png-28.7kB

接下来的三到五步很简单,代码很快就出来了,请你运行体验一下:

image.png-214.2kB

其中import math是因为使用ceil()函数需要导入math模块,使用randint()函数(随机整数)需要导入random模块

这个问题的解决还有另一种方式,在第二步的时候,你也可以运用已有知识解决。

有一个计算符不太常用,不知道你是否还记得:可以用%做取余数运算,比如print(5%4)的结果是1(5除以4余数为1)。

如果你想起了这个知识,那么在第三步,我们可以找到一个切入点:如果人数不是整数(余数不为零),就把人数用int()函数转化为整数,然后再加1。

照这个思路,写出的代码是这个样子:

image.png-318.6kB

 import math

# 工时计算
def estimated_time(size,number):
    time = size * 80 / number
    print('项目大小为%.1f个标准项目,使用%d个人力完成,则需要工时数量为:%.1f个' %(size,number,time))

# 人力计算
def estimated_number(size,time):
    number = math.ceil(size * 80 / time)
    print('项目大小为%.1f个标准项目,如果需要在%.1f个工时完成,则需要人力数量为:%d人' %(size,time,number))

# 调用工时计算函数
estimated_time(1.5,2)
# 调用人力计算函数
estimated_number(1,60)

产品经理又过来找你,这次他的需求是期望你简化代码:

image.png-250kB

我们继续用解决问题的流程来处理这个问题。

第一步,我们可以先想象一下预期效果:

# 工作量计算函数
def estimated(参数……):
    ……

# 调用工作量计算函数
estimated(参数……)

要想实现这样的效果,我们需要解决一个问题:应该怎么传递参数,才能让函数estimated(参数……)自动区分并完成工时计算和人力计算?

答对了!要区分两种不同的计算方式,当然用条件判断语句啦。

既然要用到条件判断语句,我们可以继续完善一下预期的代码结构:

# 工作量计算函数
def estimated(参数……):
    if 条件1:
        ……(人力计算)
    elif 条件2:
        ……(工时计算)

# 调用工作量计算函数
estimated(参数……)

所以,现在的问题又推进了一步:该如何设置条件,让条件1代表人力计算,条件2代表工时计算?

这个问题有多种解法,关键点是利用参数设置条件。我先跟你演示一种:(请留意代码注释)

import math

# 为函数设置了三个参数,并都带有默认参数)
def estimated(size=1,number=None,time=None):
    # 人力计算:如果参数中填了时间,没填人数,就计算人力
    if (number == None) and (time != None):
        number = math.ceil(size * 80 / time)
        print('项目大小为%.1f个标准项目,如果需要在%.1f个工时完成,则需要人力数量为:%d人' %(size,time,number))  
    # 工时计算:如果参数中填了人数,没填时间,就计算工时
    elif (number != None) and (time == None):
        time = size * 80 / number
        print('项目大小为%.1f个标准项目,使用%d个人力完成,则需要工时数量为:%.1f个' %(size,number,time))  

# 调用函数的时候,传递两个参数,会自动计算出第三个参数
estimated(size=1.5,number=2)
estimated(size=0.5,time=20.0)

在调用函数的时候,我们可以给指定的参数赋值,那剩余的参数就会是默认值(也就是在定义函数的那行定义了他们的默认值)。比如estimated(size=1.5,time=20.0),给size和time赋值,那剩下的number就默认为None。

你来运行体验一下:

image.png-371.4kB

刚才提到“合并成一个函数”这个问题不止一种解法。比如说,我们还可以这样设置三个参数来实现相同的效果:

image.png-38.4kB

感兴趣的可以将以下代码修改成刚才提到的第二种解决方案:

image.png-343.9kB

import math

def estimated(types,size,other):
    # 人力计算
    if types == 1:
        number = math.ceil(size * 80 / other)
        print('项目大小为%.1f个标准项目,如果需要在%.1f个工时完成,则需要人力数量为:%d人' %(size,other,number)) 
    # 工时计算
    elif types == 2:
        time = size * 80 / other
        print('项目大小为%.1f个标准项目,使用%d个人力完成,则需要工时数量为:%.1f个' %(size,other,time))  

estimated(1, 1.5, 2)
# 结果:项目大小为1.5个标准项目,如果需要在2.0个工时完成,则需要人力数量为:60人
estimated(2, 1.5, 2)
# 结果:项目大小为1.5个标准项目,使用2个人力完成,则需要工时数量为:60.0个

你把“稍作改良”的代码交给了产品经理。不过,这事还没完。

3.3 版本3.0:精细打磨

产品经理拿走了版本2的代码,过了几天,他又回来跟你反馈:

image.png-62.6kB

image.png-42.7kB

现在我们又拿到了新的需求:制作出“可以交互运行”的程序。

想让程序可以交互,显然要用input和print语句,这些都是我们曾经学过的。

怎么样?这个任务有多种写法,这里给你展示一种最基本的:

image.png-450.4kB

import math

def estimated(size=1,number=None,time=None):
    if (number == None) and (time != None):
        number = math.ceil(size * 80 / time)
        print('项目大小为%.1f个标准项目,如果需要在%.1f个工时完成,则需要人力数量为:%d人' %(size,time,number))  
    elif (number != None) and (time == None):
        time = size * 80 / number
        print('项目大小为%.1f个标准项目,使用%d个人力完成,则需要工时数量为:%.1f个' %(size,number,time))  

choice = input('请选择计算类型:(1-人力计算,2-工时计算)')
if choice == '1':
    size = float(input('请输入项目大小:(1代表标准大小,可以输入小数)'))
    number = None
    time = float(input('请输入工时数量:(可以输入小数)'))
    estimated(size,number,time)
elif choice == '2':
    size = float(input('请输入项目大小:(1代表标准大小,可以输入小数)'))
    number = int(input('请输入人力数量:(请输入整数)'))
    time = None
    estimated(size,number,time)

这里的思路是,先让用户输入选择计算类型,然后再用条件判断语句来区分不同的类型——用户输入1代表人力计算,用户输入2代表工时计算。当用户选择了计算类型后,再分别使用input函数采集数据,最后调用estimated()函数完成计算。

到这里,程序基本已经完成了。不过,为了展示用函数封装代码的精髓,我想再问大家一个问题:

如果要优化上面代码的结构的话,要怎么优化?

答案是:可以创建一个主函数,用来调用几个子函数。

用图片来表示的话,是这样的:

image.png-163.9kB

如图所示,我们可以把每个独立的功能封装到每个单独的函数中,然后用一个主函数打包这些单独的函数,最后再调用主函数。

你可以按照这个思路改造上面的代码:

image.png-383.5kB

import math

# 采集信息的函数
def myinput():
    choice = input('请选择计算类型:(1-人力计算,2-工时计算)')
    if choice == '1':
        size = float(input('请输入项目大小:(1代表标准大小,请输入小数)'))
        number = None
        time = float(input('请输入工时数量:(请输入小数)'))
        return size,number,time
        # 这里返回的是一个元组
    elif choice == '2':
        size = float(input('请输入项目大小:(1代表标准大小,请输入小数)'))
        number = int(input('请输入人力数量:(请输入整数)'))
        time = None
        return size,number,time
        # 这里返回的数据是一个元组

# 完成计算的函数
def estimated(my_input):
    # 把元组中的数据取出来
    size = my_input[0]
    number = my_input[1]
    time = my_input[2]
    # 人力计算
    if (number == None) and (time != None):
        number = math.ceil(size * 80 / time)
        print('项目大小为%.1f个标准项目,如果需要在%.1f个工时完成,则需要人力数量为:%d人' %(size,time,number)) 
    # 工时计算
    elif (number != None) and (time == None):
        time = size * 80 / number
        print('项目大小为%.1f个标准项目,使用%d个人力完成,则需要工时数量为:%.1f个' %(size,number,time))  

# 主函数
def main():
    my_input = myinput()
    estimated(my_input)

# 调用主函数
main()

在这里,myinput()函数负责跟用户采集信息,estimated()函数负责完成计算,而main()函数把其他两个函数打包放在一起并传递了参数。所以只要调用main()函数就能让整个程序跑起来。

之所以写成“子函数+主函数”的代码结构,也是因为每个不同的功能封装在单独的函数代码中,方便后续修改、增删。

比如我们想要加一个功能“让程序循环运行,直到用户选择结束”。那么,就可以在程序中加上一个again函数。

提示:1.需要新增变量和改造主函数;2.用到的知识是判断和循环;3.对代码进行调整是正常的(即不要期待总能一次成功)。

image.png-603.8kB

import math

# 变量key代表循环运行程序的开关
key = 1

# 采集信息的函数
def myinput():
    choice = input('请选择计算类型:(1-工时计算,2-人力计算)')
    if choice == '1':
        size = float(input('请输入项目大小:(1代表标准大小,请输入小数)'))
        number = int(input('请输入人力数量:(请输入整数)'))
        time = None
        return size,number,time
        # 这里返回的数据是一个元组
    if choice == '2':
        size = float(input('请输入项目大小:(1代表标准大小,请输入小数)'))
        number = None
        time = float(input('请输入工时数量:(请输入小数)'))
        return size,number,time
        # 这里返回的是一个元组

# 完成计算的函数
def estimated(my_input):
    # 把元组中的数据取出来
    size = my_input[0]
    number = my_input[1]
    time = my_input[2]
    # 人力计算
    if (number == None) and (time != None):
        number = math.ceil(size * 80 / time)
        print('项目大小为%.1f个标准项目,如果需要在%.1f个工时完成,则需要人力数量为:%d人' %(size,time,number)) 
    # 工时计算
    elif (number != None) and (time == None):
        time = size * 80 / number
        print('项目大小为%.1f个标准项目,使用%d个人力完成,则需要工时数量为:%.1f个' %(size,number,time))  

# 询问是否继续的函数
def again():
    # 声明全局变量key,以便修改该变量
    global key
    a = input('是否继续计算?继续请输入y,输入其他键将结束程序。')
    if a != 'y':
        # 如果用户不输入'y',则把key赋值为0
        key = 0  

# 主函数
def main():
    print('欢迎使用工作量计算小程序!')
    while key == 1:
        my_input = myinput()
        estimated(my_input)
        again()
    print('感谢使用工作量计算小程序!')

main()

4. 习题练习

4.1 习题一

1.练习介绍:
做出和电脑进行“石头剪刀布”的游戏。

2.练习要求:
和电脑玩一个剪刀石头布的游戏:电脑随机出拳,我们可选择出什么。

3.双方出拳
首先,我们要让双方选择出拳,才能判断胜负。
我们可以设置变量computer_choice代表电脑的出拳选择,设置变量user_choice代表你的出拳选择。
电脑的出拳,我们可以使用random.choice()来随机选择;我们的出拳,可以手动输入我们出拳的类型。
另外,判断下输入:当输入的内容不是石头剪刀布时,电脑会提醒'输入有误,请重新出拳',并重新出拳。

image.png-180.2kB

import random

punches = ['石头','剪刀','布']
computer_choice = random.choice(punches)
user_choice = ''
user_choice = input('请出拳:(石头、剪刀、布)')
while user_choice not in punches:
    print('输入有误,请重新出拳')
    user_choice = input()

4.双方亮拳
你和电脑已经对自己要出的拳进行了选择,接下来,我们需要知道双方的出拳类型。
请使用print()函数补充亮拳的结果。

image.png-264.9kB

import random

# 出拳
punches = ['石头','剪刀','布']
computer_choice = random.choice(punches)
user_choice = ''
user_choice = input('请出拳:(石头、剪刀、布)')  # 请用户输入选择
while user_choice not in punches:  # 当用户输入错误,提示错误,重新输入
    print('输入有误,请重新出拳')
    user_choice = input()

# 亮拳
print('————战斗过程————')
print('电脑出了:%s' % computer_choice)
print('你出了:%s' % user_choice)

5.判断胜负
在前面两步,电脑和你已经选择完出拳的类型并亮拳后,只差最后一步:根据结果判断胜负。

image.png-389.3kB

import random

# 出拳
punches = ['石头','剪刀','布']
computer_choice = random.choice(punches)
user_choice = ''
user_choice = input('请出拳:(石头、剪刀、布)')  # 请用户输入选择
while user_choice not in punches:  # 当用户输入错误,提示错误,重新输入
    print('输入有误,请重新出拳')
    user_choice = input()

# 亮拳
print('————战斗过程————')
print('电脑出了:%s' % computer_choice)
print('你出了:%s' % user_choice)

# 胜负
print('—————结果—————')
if user_choice == computer_choice:  # 使用if进行条件判断
    print('平局!')
elif (user_choice == '石头' and computer_choice == '剪刀') or (user_choice == '剪刀' and computer_choice == '布') or (user_choice == '布' and computer_choice == '石头'):
    print('你赢了!')
else:
    print('你输了!')

4.2 习题二

1.练习介绍
通过这个练习,简化上一个练习“石头剪刀布”的代码。

2.练习要求
上一个练习的代码中,有一个判断语句的代码很长很长:

elif (user_choice == '石头' and computer_choice == '剪刀') or (user_choice == '剪刀' and computer_choice == '布') or (user_choice == '布' and computer_choice == '石头'):

我们会通过一个新的知识,将其简化,体验到“知识得增加,代码得简化”这个客观规律。

3.index()函数
index() 函数用于找出列表中某个元素第一次出现的索引位置。
语法为:list.index(obj),obj为object(对象)的缩写。

image.png-128.5kB

4.代码简化

image.png-364.5kB

import random

# 出拳
punches = ['石头','剪刀','布']
computer_choice = random.choice(punches)
user_choice = ''
user_choice = input('请出拳:(石头、剪刀、布)')  # 请用户输入选择
while user_choice not in punches:  # 当用户输入错误,提示错误,重新输入
    print('输入有误,请重新出拳')
    user_choice = input()

# 亮拳
print('————战斗过程————')
print('电脑出了:%s' %(computer_choice))
print('你出了:%s' %(user_choice))

# 胜负
print('—————结果—————')
if user_choice == computer_choice:  # 使用if进行条件判断
    print('平局!')
# 请你将下一行代码用 index()函数 实现(不再有 and 和 or),从而简化代码。
elif user_choice == punches[punches.index(computer_choice)-1]:
    print('你赢了!')
else:
    print('你输了!')