• #章节八:项目实操:PK小游戏(1)


    章节八:项目实操:PK小游戏(1)

    先来谈谈一个项目一般是怎么完成的。更具体的说,程序员是如何思考和解决问题的呢?

    我认为其中一个很重要的能力是【问题拆解】。问题拆解,指的是在做一件事或面对一个问题的时候,将其拆解成多个步骤或多个层次,逐步执行和解决问题,直至达到最终效果

    当然这种拆解问题的思维并不少见。不过可能是出于长期编写代码的习惯,程序员会将问题拆得更细致一些,即拆到无法再拆为止。

    像这样,我会将完成一个项目的流程总结为以下三步:

    image.png-72.7kB

    明确项目目标,是指我们希望程序达成什么目的,实现什么功能,从而帮我们将项目拆解成不同的单元;而一个妥当的拆解方案,难度适度递增,能帮我们逐步顺利执行,最终完成项目。

    1. 明确项目目标

    在互联网公司,一般情况下是由产品经理提出明确的项目需求,由程序员来实现,他们之间是“相爱相杀”的关系。:)

    今天且让我扮演一下产品经理的角色。我们此次要实现的需求是:人机PK小游戏

    单来说,这个游戏中,会随机生成玩家和敌人的属性,同时互相攻击,直至一方血量小于零。

    另外,这样的战斗会持续三局,采取三局两胜制,最后输出战斗结果,公布获胜方。

    image.png-174.7kB

    2. 分析过程,拆解项目

    编写代码,我们无须苛求一步到位。尤其对于刚接触编程的学习者来说,层层递进、逐渐提升难度才能达到更好的练习效果。

    先从“功能叠加、难度递增”这个角度考虑,将我们要实现的小游戏拆分成了三个版本。

    image.png-107.3kB

    版本1.0,主要是帮我们理清战斗逻辑。而版本2.0和3.0,会涉及到一些新的知识点,到时遇到了再和大家介绍。

    当项目被清晰地拆解后,剩下的就是去逐步执行,也就是重复“执行→遇到问题→解决问题→继续执行”这个循环的过程。

    下面,开始正式写代码咯,让我们一个一个版本来攻克吧!

    3. 逐步执行,代码实现

    3.1 版本1.0:自定属性,人工PK

    第一阶段的代码,我们的主要任务是理清战斗的逻辑,再用print()函数将战斗过程打印在终端。

    我们先来思考一下,一个人机PK游戏最基础的元素是什么,我们可以拿最经典的拳皇游戏来脑补一下。

    image.png-67.5kB

    根据这一版本的设定,我们要做的主要有三步:
    1.规定并显示出玩家和敌人的属性
    2.双方同时互相攻击,血量根据对方的攻击力扣除
    3.若有一方血量小于等于0,游戏结束。

    为了让我们的思路保持清晰,画成流程图就是这样子的:

    image.png-78.1kB

    别说你平时不玩游戏,不知道该怎么动手。这个版本的所有步骤,都还很不“智能”,只用到了唯一一个函数Print()。也就是说,我们只要把步骤一个一个打印上去,就算成功啦。

    好,我们从第1步开始:设定【玩家】和【敌人】的属性,即【血量】和【攻击】。

    print('【玩家】血量:100 攻击:50')  # 自定义玩家角色的血量和攻击
    print('【敌人】血量:100 攻击:30')  # 自定义敌人角色的血量和攻击
    

    第2步:手动计算攻击一次,双方各自所剩的血量。

    print('你发起了攻击,【敌人】剩余血量50')  # 人工计算敌人血量:100-50=50
    print('敌人向你发起了攻击,【玩家】剩余血量70')  # 人工计算玩家血量:100-30=70
    

    第3步:继续做人工计算:算一算,玩家攻击2次敌人,敌人的血量就等于0了,这时候可以结束战斗,打印游戏结果。

    print('你发起了攻击,【敌人】剩余血量0')  # 双方同时攻击,若血量出现小于等于0,游戏结束
    print('敌人向你发起了攻击,【玩家】剩余血量40')
    
    print('敌人死翘翘了,你赢了!') # 打印结果
    

    很简单吧!现在我们要做的,就是把这三段代码拼起来,然后我会加一些修饰视觉的换行符和分割线,让运行结果看得更清楚一点。

    print('【玩家】\n血量:100\n攻击:50')  # 自定义玩家角色的血量和攻击,用换行符'\n'来优化视觉
    print('------------------------')  # 辅助功能,起到视觉分割的作用,让代码的运行结果更清晰
    
    print('【敌人】\n血量:100\n攻击:30')
    print('------------------------')
    
    print('你发起了攻击,【敌人】剩余血量50')  # 人工计算敌人血量:100-50=50
    print('敌人向你发起了攻击,【玩家】剩余血量70')  # 人工计算玩家血量:100-30=70
    print('------------------------')
    
    print('你发起了攻击,【敌人】剩余血量0')  # 双方同时攻击,若血量出现小于等于0,游戏结束
    print('敌人向你发起了攻击,【玩家】剩余血量40')
    print('-----------------------')
    
    print('敌人死翘翘了,你赢了!') # 打印结果
    

    来看下执行结果:

    image.png-410.7kB

    唔...虽然看起来还有点儿意思,但所有信息一下子都蹦跶出来,一点都没有体现游戏的进程感。

    所以,为了让打印出的东西能有时间间隔地依次出现,我们需要设置一个类似“计时器”的东西。在Python里,我们需要用到两行代码来实现:(敲黑板,很简单的新知识)

    import time   #调用time模块
    time.sleep(secs)   
    #使用time模块下面的sleep()函数,括号里填的是间隔的秒数(seconds,简称secs)
    #time.sleep(1.5)就表示停留1.5秒再运行后续代码
    

    这里有个新名词——模块,它是Python里一个重要的概念。

    你可以把模块想象成是一个装着许多神奇函数的百宝箱,不过想要使用这个百宝箱里的函数,得先用 import 模块名 这样一句代码来打开它。

    然后这里我们想使用time模块里的sleep()函数,也就是让代码运行结果不要一次性全部出现,而是分批分批的出现。就要写成time.sleep(secs)的形式

    如果我想设置成打印的信息间隔1.5秒出现,代码就可以这么写:

    import time  #通常import语句会写到代码的开头
    
    print('【玩家】\n血量:100\n攻击:50')  
    print('------------------------')  
    time.sleep(1.5)
    #暂停1.5秒,再继续运行后面的代码
    
    print('【敌人】\n血量:100\n攻击:30')
    print('------------------------')
    time.sleep(1.5)
    #同上
    
    print('你发起了攻击,【敌人】剩余血量50')  
    print('敌人向你发起了攻击,【玩家】剩余血量70') 
    print('------------------------')
    time.sleep(1.5)
    
    print('你发起了攻击,【敌人】剩余血量0')  
    print('敌人向你发起了攻击,【玩家】剩余血量40')
    print('-----------------------')
    time.sleep(1.5)
    
    print('敌人死翘翘了,你赢了!') 
    

    呼~总算完成了版本1.0,不过我想你一定在心里默默吐槽,一句句用print()写也太蠢太弱鸡了吧。

    没错,不过代码嘛,总得一步步实现。就先当作是小小的热身。

    而且,这个版本的代码还有两个明显的缺陷:一是玩家和敌人的属性(血量&攻击)是我自己说了算,那胜负早已没有悬念;二是战斗过程中血量的变化要自己手动算,那要计算机有何用?

    你放心,这些都是我们会在版本2.0解决的问题

    3.2 版本2.0:随机属性,自动PK

    如前所述,这个阶段,我们主要新增【随机属性】和【自动战斗】两个功能,画成流程图是这样子的:

    image.png-83.7kB

    想一想:自己来定义双方角色的属性,那简直是黑箱操作,胜负早已注定。所以,为了游戏公平,我们要让属性由自己说了算变成随机生成。

    现在问题来了,要随机生成属性(数字),又该怎么办嘞?

    image.png-139.3kB

    我们看这段文字可以发现,要随机生成整数,就要用到random模块里的randint()函数,括号里放的是两个整数,划定随机生成整数的范围。

    可以多运行几次,看看结果是不是随机生成的~

    image.png-276.6kB

    请听题:
    1.定义两个变量,来存储玩家血量和玩家攻击力的数值
    2.血量是100-150的随机数,攻击力是30-50的随机数
    3.将两个变量打印出来

    image.png-282.1kB

    好,我们已经知道如何生成随机属性,下面我们就要将属性展示打印出来,请阅读下列代码,弄懂每一行的含义:

    import time
    import random
    #也可合并写成一行:import time,random
    
    # 生成随机属性
    player_life = random.randint(100,150) # “player_life” 代表玩家血量
    player_attack = random.randint(30,50) # “player_attack” 代表玩家攻击
    enemy_life = random.randint(100,150) # “enemy_life” 代表敌人血量
    enemy_attack = random.randint(30,50) # “enemy_attack” 代表敌人攻击
    
    # 展示双方角色的属性
    print('【玩家】\n'+'血量:'+str(player_life)+'\n攻击:'+str(player_attack))
    #player_life和player_attack的数据类型都是整数,所以拼接时需要先用str()转换
    print('------------------------')
    time.sleep(1)
    #暂停一秒再执行后续代码
    print('【敌人】\n'+'血量:'+str(enemy_life)+'\n攻击:'+str(enemy_attack))
    print('------------------------')
    

    那截至目前,我们已经完成了随机生成属性和展示属性,接下来我们就来实现"自动战斗"。

    image.png-83.9kB

    要怎么实现自动战斗呢?如果一头雾水的话,可以先尝试从版本1.0的人为战斗来寻找规律:

    print('【玩家】 血量:130  攻击:50')  
    print('【敌人】 血量:150  攻击:40')
    
    print('你发起了攻击,【敌人】剩余血量100')  
    print('敌人向你发起了攻击,【玩家】剩余血量90') 
    print('------------------------')
    
    print('你发起了攻击,【敌人】剩余血量50')  
    print('敌人向你发起了攻击,【玩家】剩余血量70')
    print('-----------------------')
    
    print('你发起了攻击,【敌人】剩余血量0')  
    print('敌人向你发起了攻击,【玩家】剩余血量40')
    print('-----------------------')
    
    print('敌人死翘翘了,你赢了!')
    

    我们可以发现,4-6行这3行是重复出现的结构,除了数字是灵活变动之外,其余是一毛一样的。

    那么,我们应该用什么来解决重复劳动?

    循环语句

    答对了!接下来可以进一步思考是该用for循环还是while循环。

    说到循环,我们就要思考是要使用for循环还是while循环了。

    因为现在双方的血量和攻击是随机生成,不是固定的。所以我们不知道具体要战斗多少回合才能分出胜负,也就是循环次数不明确,那自然要用while循环

    我们进一步思考:while后面要接什么条件呢,也就是说什么条件下,战斗过程会一直持续呢?

    如果有一方血量小于等于0,战斗就结束了。

    所以我们现在确定了让循环执行需要满足的条件就是——双方血量均大于零,也就是不死不休。

    image.png-59.5kB

    可见while后面要同时满足两个条件,即这两个条件要同时为真,所以我们要用and来连接,用代码来表示就是:

    # and两边的条件分别用括号括起,是一种习惯,方便阅读
    while (player_life > 0) and (enemy_life > 0):
    

    现在我们确定了执行while循环的条件,接下来就是要填充循环内部的内容。

    根据刚才的分析,我们希望循环的内容是双方互相攻击,掉血的过程。

    print('你发起了攻击,【敌人】剩余血量xxx')  
    print('敌人向你发起了攻击,【玩家】剩余血量xxx') 
    print('------------------------')
    

    其中【敌人】剩余血量=敌人当前血量-玩家攻击,【玩家】剩余血量=玩家当前血量-敌人攻击。

    事实上我们之前已经定义好了这四个变量,每一次互相伤害后,player_life(玩家血量)和enemy_life(敌人血量)都会被重新赋值,所以转换为代码逻辑就是:

    player_life = player_life - enemy_attack 
    enemy_life = enemy_life - player_attack 
    #赋值语句的执行顺序是先计算等号右边,再赋值给左边的变量
    icon
    好,自动攻击的基础逻辑也已经理清楚了。我们先合并一下这之前写过的代码。
    
    import time,random
    
    # 生成随机属性
    player_life = random.randint(100,150) 
    player_attack = random.randint(30,50) 
    enemy_life = random.randint(100,150) 
    enemy_attack = random.randint(30,50) 
    
    # 展示双方角色的属性
    print('【玩家】\n'+'血量:'+str(player_life)+'\n攻击:'+str(player_attack))
    #player_life和player_attack都是整数类型,所以拼接时需要先用str()转换
    print('------------------------')
    time.sleep(1)
    print('【敌人】\n'+'血量:'+str(enemy_life)+'\n攻击:'+str(enemy_attack))
    print('------------------------')
    time.sleep(1)
    
    while (player_life >0) and (enemy_life > 0):
        player_life = player_life - enemy_attack 
        enemy_life = enemy_life - player_attack 
    

    接下来,我们只需要补充完成while循环语句,让双方自动战斗、扣血的过程循环起来。

    import time,random
    
    player_life = random.randint(100,150) 
    player_attack = random.randint(30,50) 
    enemy_life = random.randint(100,150) 
    enemy_attack = random.randint(30,50) 
    
    print('【玩家】\n'+'血量:'+str(player_life)+'\n攻击:'+str(player_attack))
    print('------------------------')
    time.sleep(1)
    print('【敌人】\n'+'血量:'+str(enemy_life)+'\n攻击:'+str(enemy_attack))
    print('------------------------')
    time.sleep(1)
    
    while (player_life >0) and (enemy_life > 0):
        player_life = player_life - enemy_attack 
        enemy_life = enemy_life - player_attack 
        print('你发起了攻击,【敌人】剩余血量'+str(enemy_life))
        #player_life是整数,所以拼接时要先用str()转换
        print('敌人向你发起了攻击,【玩家】剩余血量'+str(player_life))
        print('------------------------')
        time.sleep(1.5)
        # 为了体现出战斗回合,这里停顿1.5秒  
    

    来看看执行结果:

    image.png-442.2kB

    你应该能感受到,版本2.0总算像模像样了,慢慢逼近我们的项目目标。

    不过它还没有实现:打印出每局结果,三局两胜,并打印最终战果的功能。

    3.3 版本3.0:打印战果,三局两胜

    对比版本2.0,在版本3.0中,我们想要增加的功能是:1.打印战果:每局战斗后,根据胜负平的结果打印出不同的提示;2.三局两胜:双方战斗三局,胜率高的为最终赢家。

    image.png-112.3kB

    结果是有三种可能性的,对应的条件如下图所示:

    image.png-73.7kB

    来试试为游戏增加“打印战果”(单局)的功能

    import time,random
    
    # 生成双方角色,并生成随机属性。
    player_life = random.randint(100,150)
    player_attack = random.randint(30,50)
    enemy_life = random.randint(100,150)
    enemy_attack = random.randint(30,50)
    
    # 展示双方角色的属性
    print('【玩家】\n'+'血量:'+str(player_life)+'\n攻击:'+str(player_attack))
    print('------------------------')
    time.sleep(1)
    print('【敌人】\n'+'血量:'+str(enemy_life)+'\n攻击:'+str(enemy_attack))
    print('------------------------')
    time.sleep(1)
    
    # 双方PK
    while player_life > 0 and enemy_life > 0:
        player_life = player_life - enemy_attack
        enemy_life = enemy_life - player_attack
        print('你发起了攻击,【敌人】剩余血量'+str(enemy_life))
        print('敌人向你发起了攻击,【玩家】剩余血量'+str(player_life))
        print('-----------------------')
        time.sleep(1.5)
    
    # 打印战果
    if player_life > 0 and enemy_life <= 0:
        print('敌人死翘翘了,你赢了')
    elif player_life <= 0 and enemy_life > 0:
        print('悲催,敌人把你干掉了!')
    else:
        print('哎呀,你和敌人同归于尽了!')
    

    来看看执行结果:

    image.png-393.9kB

    好啦,现在版本3.0也只剩最后的“三局两胜”了?

    image.png-113.4kB

    同样的,我们可以将其拆分成两个部分:先来个三局,再判断最终胜负。

    image.png-73.4kB

    首先我们来看,三局战斗也是一个可以循环的结构,且循环次数是固定的,所以要用到for循环。

    在这里我们可以使用for i in range( )的结构,我们先来回顾一下之前学过的range()函数:

    image.png-114.5kB

    给两个提示:
    1.想清楚哪些代码要嵌套到for循环里,即一局战斗里包括什么信息。确定了for写在哪里之后,一局战斗包含的所有信息都要缩进;
    2.细节也需要留意,如局与局之间要怎么区分开来(时间间隔&打印局数信息)

    import time,random
    
    for i in range(1,4):
        time.sleep(1.5)  # 让局与局之间有较明显的有时间间隔
        print(' \n——————现在是第'+str(i)+'局,ready go!——————')  # 作为局的标记
     
        player_life = random.randint(100,150)
        player_attack = random.randint(30,50)
        enemy_life = random.randint(100,150)
        enemy_attack = random.randint(30,50)
    
        # 展示双方角色的属性
        print('【玩家】\n'+'血量:'+str(player_life)+'\n攻击:'+str(player_attack))
        print('------------------------')
        time.sleep(1)
        print('【敌人】\n'+'血量:'+str(enemy_life)+'\n攻击:'+str(enemy_attack))
        print('------------------------')
        time.sleep(1)
    
        # 双方PK
        while player_life > 0 and enemy_life > 0:
            player_life = player_life - enemy_attack
            enemy_life = enemy_life - player_attack
            print('你发起了攻击,【敌人】剩余血量'+str(enemy_life))
            print('敌人向你发起了攻击,【玩家】剩余血量'+str(player_life))
            print('-----------------------')
            time.sleep(1.5)
    
        # 打印战果
        if player_life > 0 and enemy_life <= 0:
            print('敌人死翘翘了,你赢了')
    
        elif player_life <= 0 and enemy_life > 0:
            print('悲催,敌人把你干掉了!')
        else:
            print('哎呀,你和敌人同归于尽了!')
    

    如果做起来有些障碍,检查一下是否存在上面提示的这几个问题:

    1. for循环语句的位置放的对不对?这个关键在于,你想让哪些信息被循环展示。例如:如果你错将for循环语句放在了【随机属性】 和【自动战斗】之间,那每一局的战斗信息会是一样的,也就不存在什么三局两胜了。

    2.你写完for循环语句后,需要缩进的信息【整体】缩进了吗?如果没有缩进,可能存在报错,或者只有部分战斗信息循环的情况。

    3.细节注意到了吗?局与局之间要有明显间隔,那我们可以同时使用time.sleep()和print('现在是第x局')来完美解决这个问题。此外,遇到各种报错的话,记得去搜索一下,看看报的是什么错,先自己尝试解决看看。

    好,现在来看我的答案,请你主要看3-5行(后面内容都要放在for循环内部),然后运行看看效果。

    image.png-642.6kB

    OK,打三局这个需求也成功了。现在我们距离最后的终点只剩一步之遥,只有“统计三局两胜的结果”这个功能还没实现了。

    我们可以想一想,平常我们是怎么统计比赛结果呢?

    image.png-603.1kB

    好比乒乓球比赛,有一方赢了一局就翻一下计分牌,让数字+1,最后看哪边的数字大就是哪边获胜。

    对于计算机也是如此:它靠数据思考,比如拿数据做计算、做条件判断、做循环等。所以这里的关键就在于,要给计算机数据。

    那么仿照计分牌的做法,我们的解决方法也就出来了:采取计分的方式,赢一局记一分,平局不计分。

    所以,我们要给计算机一个空白的“计分板”,用于存放【玩家】和【敌人】每一局的得分。

    player_victory = 0
    #存放玩家赢的局数。
    enemy_victory = 0
    #存放敌人赢的局数
    

    那什么情况下,这两个变量会变动(+1)呢?自然是要与具体每一局的结果挂钩,这时候可以回看我们计算输赢的条件判断语句。

    if player_life > 0 and enemy_life <= 0:  #玩家赢
        print('敌人死翘翘了,你赢了')
    elif player_life <= 0 and enemy_life > 0: #敌人赢
        print('悲催,敌人把你干掉了!')
    else:                                    #平局
        print('哎呀,你和敌人同归于尽了!')
    

    然后,我们将敌人和玩家各自赢的局数给算出来:

    player_victory = 0
    enemy_victory = 0
    
    if player_life > 0 and enemy_life <= 0:
        player_victory = player_victory + 1
        print('敌人死翘翘了,你赢了!')
    elif player_life <= 0 and enemy_life > 0:
        enemy_victory  = enemy_victory + 1
        print('悲催,敌人把你干掉了!')
    else:
        print('哎呀,你和敌人同归于尽了!')
    

    这样三局过后,player_victory和enemy_victory会被赋上新的值。给你一个小技巧:player_victory = player_victory + 1,总是这样写有点烦人,我们可以写作player_victory += 1,这两个代码是等价的,都代表"如果if后的条件满足,变量就+1"

    这也是程序员是追求“极简”的体现。好,我们把这段代码替换一下:

    player_victory = 0
    enemy_victory = 0
    
    if player_life > 0 and enemy_life <= 0:
        player_victory += 1
        print('敌人死翘翘了,你赢了!')
    elif player_life <= 0 and enemy_life > 0:
        enemy_victory  += 1
        print('悲催,敌人把你干掉了!')
    else:
        print('哎呀,你和敌人同归于尽了!')
    

    现在,我们只需要再用一次条件判断,比较两个变量的大小就能知道谁输谁赢了。

    我会把思维逻辑展示给你。注意思考:这一次的条件判断,要不要缩进呢?

    image.png-90.1kB

    import time,random
    
    player_victory = 0
    enemy_victory = 0
    
    for i in range(1,4):
        time.sleep(2)  # 让局与局之间有较明显的有时间间隔
        print(' \n——————现在是第'+str(i)+'局——————')  # 作为局的标记
     
        player_life = random.randint(100,150)
        player_attack = random.randint(30,50)
        enemy_life = random.randint(100,150)
        enemy_attack = random.randint(30,50)
    
        # 展示双方角色的属性
        print('【玩家】\n'+'血量:'+str(player_life)+'\n攻击:'+str(player_attack))
        print('------------------------')
        time.sleep(1)
        print('【敌人】\n'+'血量:'+str(enemy_life)+'\n攻击:'+str(enemy_attack))
        print('------------------------')
        time.sleep(1)
    
        # 双方PK
        while player_life > 0 and enemy_life > 0:
            player_life = player_life - enemy_attack
            enemy_life = enemy_life - player_attack
            print('你发起了攻击,【敌人】剩余血量'+str(enemy_life))
            print('敌人向你发起了攻击,【玩家】剩余血量'+str(player_life))
            print('-----------------------')
            time.sleep(1.5)
    
        #打印最终战果
        if player_life > 0 and enemy_life <= 0:
            player_victory += 1
            print('敌人死翘翘了,你赢了!')
        elif player_life <= 0 and enemy_life > 0:
            enemy_victory += 1
            print('悲催,敌人把你干掉了!')
        else:
            print('哎呀,你和敌人同归于尽了!')
    
    if player_victory > enemy_victory :
        time.sleep(1)
        print('【最终结果:你赢了!】')
    elif enemy_victory > player_victory:
        print('【最终结果:你输了!】')
    else: 
        print('【最终结果:平局!】')
    

    来看看执行效果:

    image.png-588.1kB

    image.png-620.7kB

    还是回顾一下最后一步,将条件判断的思维逻辑转换成代码逻辑的话是这样子的:

    image.png-128.9kB

    4. 知识扩展

    作为一个程序员,代码是我们的名片,我们会追求更加优雅的,方便他人阅读的代码,所以上述代码还有一些优化空间。

    所以,以下是彩蛋时间,我会教大家一个新的知识点——【格式化字符串】。

    什么意思呢,上面有这么两行代码,是用来展示双方角色的属性的:

    print('【玩家】\n'+'血量:'+str(player_life)+'\n攻击:'+str(player_attack))
    print('【敌人】\n'+'血量:'+str(enemy_life)+'\n攻击:'+str(enemy_attack))
    

    我们在用+拼接字符串和变量的时候,常常需要考虑变量是什么类型的数据,如果不是字符串类型,还先需要str()函数转换。

    并且一句话常常要拼接成好几个部分,然后我们要考虑每一对引号' '的起始位置,好麻烦,相信你多少会有点体会。

    所以,为了更方便地实现不同数据类型的拼接,用【格式符%】是更常用更便利的一种方式。

    我们可以把%想象成:图书馆里用来占位的一本书。先占一个位置,之后再填上实际的变量。举个例子:下面这两种写法是相同的,请你着重研究下第二行的语法。

    print('血量:'+str(player_life)+' 攻击:'+str(player_attack))
    print('血量:%s 攻击:%s' % (player_life,player_attack))
    

    我们看到格式符%后面有一个字母s,这是一个类型码,用来控制数据显示的类型。%s就表示先占一个字符串类型的位置。

    还有其他常见的类型码如下图所示:

    image.png-65.1kB

    占完位置之后,我们要以%的形式在后面补上要填充的内容,如此一来我们就免去了转换类型的烦恼。如果是多个数据,就要把它们放进括号,按顺序填充,用逗号隔开。

    举个例子,对比下列输出的结果:

    image.png-199.2kB

    一个小小的提示:%后面的类型码用什么,取决于你希望这个%占住的这个位置的数据以什么类型展示出来,如果你希望它以字符串形式展示,那就写%s,如果你希望它以整数形式展示,那就写%d。

    这就出现了一些容易混淆的地方,比如,请运行以下的代码:

    image.png-173.6kB

    选用了不同的类型码,打印出的结果却是一样,原因也已经在代码注释中写清楚了:因为整数8与字符串'8'的打印结果是一样的,所以选两种类型码都OK。但这种“都OK”的情况仅限于整数,对文字是行不通的:

    image.png-242.8kB

    会报错,对吧?好啦,现在你应该更懂这个格式化字符串该怎么用了。

    那就看回我们之前的代码,如果把一开始用+拼接的字符串都替换成%格式符表示,我们先替换一部分试试

    print(' \n——————现在是第'+str(i)+'局——————') #替换前
    print('  \n——————现在是第 %s 局——————' % i) #替换后
    
    print('【玩家】\n'+'血量:'+str(player_life)+'\n攻击:'+str(player_attack))  #替换前
    print('【玩家】\n血量:%s\n攻击:%s' % (player_life,player_attack)) #替换后
    
    print('敌人发起了攻击,【玩家】剩余血量'+str(player_life)) #替换前
    print('敌人发起了攻击,【玩家】剩余血量%s' % player_life) #替换后
    

    你也许想问我,这里的%s是不是都能换成%d,答案是“YES!”,因为这里的变量i,player_life,player_attack统统都是整数。

    好,我们这次先选择使用%s,来看完整的代码:

    import time
    import random
    
    player_victory = 0
    enemy_victory = 0
    
    for i in range(1,4):
        time.sleep(1.5)
        print('  \n——————现在是第 %s 局——————' % i)
        #对比之前:(' \n——————现在是第'+str(i)+'局——————')
        player_life = random.randint(100,150)
        player_attack = random.randint(30,50)
        enemy_life = random.randint(100,150)
        enemy_attack = random.randint(30,50)
    
        print('【玩家】\n血量:%s\n攻击:%s' % (player_life,player_attack))
        print('------------------------')
        time.sleep(1)
        print('【敌人】\n血量:%s\n攻击:%s' % (enemy_life,enemy_attack))
        print('-----------------------')
        time.sleep(1)
    
        while player_life > 0 and enemy_life > 0:
            player_life = player_life - enemy_attack 
            enemy_life = enemy_life - player_attack
            print('你发起了攻击,【敌人】剩余血量%s' % enemy_life)
            print('敌人向你发起了攻击,【玩家】的血量剩余%s' % player_life)
            print('-----------------------')
            time.sleep(1.2)
    
        if player_life > 0 and enemy_life <= 0:
            player_victory += 1
            print('敌人死翘翘了,你赢了!')
        elif player_life <= 0 and enemy_life > 0:
            enemy_victory += 1
            print('悲催,敌人把你干掉了!')
        else:
            print('哎呀,你和敌人同归于尽了!')
    
    if player_victory > enemy_victory :
        time.sleep(1)
        print('\n【最终结果:你赢了!】')
    elif enemy_victory > player_victory:
        print('\n【最终结果:你输了!】')
    else: 
        print('\n【最终结果:平局!】')
    

    是不是看起来清爽了些?如果还不习惯这种表示方法没有关系,多写几次就习惯啦。

    现在我保证真是最后一步了,感受一下我们这一路走来的成果吧。

    image.png-568.5kB

    image.png-621.3kB

    最后我们再来回顾一下做项目的三个步骤。

    image.png-73.1kB

    5. 习题练习

    5.1 习题一

    1.练习目标:
    我们会在项目1代码的基础上,添加一个新功能,同时练习循环和判断的知识。

    2.练习要求:
    新功能是:每盘(3局)游戏结束后,游戏会问我们是否要继续游戏,再加一盘。
    我们可以选择再来一盘,也可以选择退出游戏。

    3.进一步拆解和分析
    对于新功能,我们也可以将其拆解一下:

    image.png-71.3kB

    注:游戏过程的代码要放进这个while循环里面,先判断,再执行。

    要达成目标,有两种方案:
    方案一 while True+break:
    开启一个无限循环,设定跳出条件。
    当得到肯定回复时,继续运行;当得到否定回复时,运行break,停止循环,退出游戏。

    方案二 while 变量名+变量名的布尔值判断:
    在开头设某变量的布尔值为True,input后开启判断变量的布尔值是否改变。
    当得到肯定回复时,while True,继续运行;当得到否定回复时,while False,停止循环,退出游戏。

    4.方案1:while True+break
    开启一个无限循环,设定跳出条件。

    while True:
        a1 = input('要继续游戏吗,请输入n退出,输入其他继续:')
        if a1 == 'n':
            break
    print('再见')
    

    image.png-221kB

    5.方案2:while 变量名+变量名的布尔值判断
    在开头设某变量的布尔值为True,input后开启判断变量的布尔值是否改变。

    again = True
    while again:
        a2 = input('要继续游戏吗,请输入y继续,输入其他退出:')
        if a2 == 'y':
            again = True
            break
        else:
            again = False
    print(again)
    

    image.png-274.6kB

    6.两种方案-参考代码

    # 方案1
    while True:
        a1 = input('要继续游戏吗,请输入n退出,输入其他继续:')
        if a1 == 'n':
            break
    
    # 方案2
    again = True
    while again:
        a2 = input('要继续游戏吗,请输入y继续,输入其他退出:')
        if a2 == 'y':
            again = True
        else:
            again = False
    

    image.png-369.9kB

    7.查看一下完整版的代码吧

    import time
    import random
    
    while True:
        player_victory = 0
        enemy_victory = 0
    
        for i in range(1,4):
            time.sleep(1.5)
            print('  \n——————现在是第 %s 局——————' % i)
            player_life = random.randint(100,150)
            player_attack = random.randint(30,50)
            enemy_life = random.randint(100,150)
            enemy_attack = random.randint(30,50)
    
            print('【玩家】\n血量:%s\n攻击:%s' % (player_life,player_attack))
            print('------------------------')
            time.sleep(1)
            print('【敌人】\n血量:%s\n攻击:%s' % (enemy_life,enemy_attack))
            print('-----------------------')
            time.sleep(1)
    
            while player_life > 0 and enemy_life > 0:
                player_life = player_life - enemy_attack 
                enemy_life = enemy_life - player_attack
                print('你发起了攻击,【敌人】剩余血量%s' % enemy_life)
                print('敌人向你发起了攻击,【玩家】的血量剩余%s' % player_life)
                print('-----------------------')
                time.sleep(1.2)
    
            if player_life > 0 and enemy_life <= 0:
                player_victory += 1
                print('敌人死翘翘了,你赢了!')
            elif player_life <= 0 and enemy_life > 0:
                enemy_victory += 1
                print('悲催,敌人把你干掉了!')
            else:
                print('哎呀,你和敌人同归于尽了!')
    
        if player_victory > enemy_victory :
            time.sleep(1)
            print('\n【最终结果:你赢了!】')
        elif enemy_victory > player_victory:
            print('\n【最终结果:你输了!】')
        else: 
            print('\n【最终结果:平局!】')
    
        a1 = input('要继续游戏吗,请输入n退出,输入其他继续:')
        if a1 == 'n':
            break
    

    5.2 习题二

    1.练习目标
    在这个练习,我们会学会一种新的“格式化字符串”的方法:format()函数。

    2.练习要求
    在项目1的末尾,我们学会了一种简化代码的方式:格式化字符串。
    不过,还有一种更强大的方法,下面我们会先学习,然后再练习。

    3.学习format()函数
    format()函数是从 Python2.6 起新增的一种格式化字符串的函数,功能很强大。
    format()函数用来占位的是大括号{},不用区分类型码(%+类型码)
    具体的语法是:'str.format()',而不是之前提到的'str % ()'。
    而且,它对后面数据的引用更灵活,不限次数,也可指定对应关系。

    # % 格式化:str % ()
    print('%s%d'%('数字:',0))
    print('%d,%d'%(0,1))
    print('%d,%d,%d'%(0,1,0))
    
    name1 = 'Python'
    print('I am learning %s'% name1)  # 注:当只跟一个数据时,%后可不加括号,format()一定要有。
    
    # format()格式化函数:str.format()
    print('\n{}{}'.format('数字:',0))  # 优势1:不用担心用错类型码。
    print('{},{}'.format(0,1))  # 不设置指定位置时,默认按顺序对应。
    print('{1},{0}'.format(0,1))  # 优势2:当设置指定位置时,按指定的对应。
    print('{0},{1},{0}'.format(0,1))  # 优势3:可多次调用format后的数据。
    
    name2 =  'Python基础语法'
    print('我正在学{}'.format(name2))  # format()函数也接受通过参数传入数据。
    

    image.png-358.1kB

    4.运用format()函数

    以下是参考代码

    import time
    import random
    
    player_victory = 0
    enemy_victory = 0
    
    while True:
        for i in range(1,4):
            time.sleep(1.5)
            print('  \n——————现在是第 {} 局——————'.format(i))
            player_life = random.randint(100,150)
            player_attack = random.randint(30,50)
            enemy_life = random.randint(100,150)
            enemy_attack = random.randint(30,50)
    
            print('【玩家】\n血量:{}\n攻击:{}'.format(player_life,player_attack))
            print('------------------------')
            time.sleep(1)
            print('【敌人】\n血量:{}\n攻击:{}'.format(enemy_life,enemy_attack))
            print('-----------------------')
            time.sleep(1)
    
            while player_life > 0 and enemy_life > 0:
                player_life = player_life - enemy_attack 
                enemy_life = enemy_life - player_attack
                print('敌人发起了攻击,【玩家】剩余血量{}'.format(player_life))
                print('你发起了攻击,【敌人】的血量剩余{}'.format(enemy_life))
                print('-----------------------')
                time.sleep(1.2)
    
            if player_life > 0 and enemy_life <= 0:
                player_victory += 1
                print('敌人死翘翘了,你赢了!')
            elif player_life <= 0 and enemy_life > 0:
                enemy_victory += 1
                print('悲催,敌人把你干掉了!')
            else:
                print('哎呀,你和敌人同归于尽了!')
    
        if player_victory > enemy_victory :
            time.sleep(1)
            print('\n【最终结果:你赢了!】')
        elif enemy_victory > player_victory:
            print('\n【最终结果:你输了!】')
        else: 
            print('\n【最终结果:平局!】')
    
        a1 = input('要继续游戏吗,请输入n退出,输入其他继续:')
        if a1 == 'n':
            break
    
  • 相关阅读:
    20211111避免对需求、功能理解断层问题的思考
    20211216部门日报综述优化建议
    想买二手房,听说房子过户了也可能住不进去,怎么避免?
    20220104tapd需求与测试用例打“作废”标记建议
    wps的SUM函数计算失败问题
    SQL注入测试总结
    缺陷标题
    12.6 Markdown高级技巧
    学期内容的总结
    12.5Markdown高级技巧
  • 原文地址:https://www.cnblogs.com/ywb123/p/16357436.html
Copyright © 2020-2023  润新知