通过游戏编程学Python(3)— 赌大小

通过游戏编程学Python

通过游戏编程学Python(2)— 脑筋急转弯
通过游戏编程学Python(1)— 猜数字



前言

大家好,上节课我们学习的内容虽然可能比较简单,但希望大家也都能实际动手敲敲代码(而不是复制粘贴),只有动手了,你才会发现各种各样的小问题。有时候少了半个括号“( )”,或是少了if或while语句后面的问号“:”,有时候同一个代码块的缩进不一致,等等。最需要注意的是不要误用了中文的全角符号,虽然我们可以使用中文来作为变量名,或是单纯地输入输出中文字符串,但当用在Python语法中,Python只认识半角的括号“( )”、冒号“:”等,这一点要格外小心。

此外,上节课的内容其实算不上游戏,相信大家或许都有些不甘吧?但为了将一些基本概念讲清楚,问哥还是觉得有必要列出上一章。今天,相信大家已经掌握了一定的基础知识,于是让我们认真地编写一个真正的小游戏吧。

让我们回到二十年前的MUD时代。。。额,暴露了问哥的年龄。。。想象一下,在DOS环境下用纯粹的文本来玩RPG(角色扮演类游戏),你控制着一个角色,通过键盘输入指令,让他前进、后退、寻宝、战斗等等,屏幕上带着节奏般缓缓跳出的信息,陪伴了问哥多少个无眠的夜晚。。。先别急,总有一天,待我们掌握所有必要的知识后,你也可以亲手打造一个RPG世界。但是现在,让我们的角色先走进一个虚拟的赌场,去和计算机用骰子赌大小吧。


一、知识点

  1. time.sleep()函数
  2. 自定义函数
  3. 全局变量与局部变量
  4. 实参与形参
  5. 布尔型变量与逻辑运算

二、第三个游戏 —— 赌大小

1. 玩法简介

通过一行行跳动的文字而徐徐展开的背景里,我们不知什么原因已坐在了赌桌旁。站在对面的庄家皮笑肉不笑地望着你,问:押大还是小?当你做出选择后,他摇动三粒骰子,掷出了点数。规则很简单:三粒骰子点数之和最小为3,最大为18,所以3到10点为小,11到18点为大。看看你的运气如何吧!

2. 游戏流程

yes
no
no
yes
游戏开始
背景介绍
玩家下注
庄家掷骰子
判断胜负
“你赢了”
还玩吗?
“你输了”
游戏结束

三、程序代码

全部代码如下:

import random
import time

def 背景介绍():
    print('此时已是后半夜,喝得醉醺醺的你,蹒跚着走在这条黑漆漆的小路上。')
    time.sleep(2)
    print('道路的尽头仍然亮着一间瓦房,里面传出的呼喊声此起彼伏。')
    time.sleep(2)
    print('“原来是家赌场。”你一边心里想着,一边走了进去。')
    time.sleep(2)
    print('庄家满脸堆着笑,热情地招呼你过去:“来来来,这边有空位!”')
    time.sleep(2)
    print('你神情恍惚地抓了张椅子,在赌桌旁坐下。')
    time.sleep(2)

def 下注():
    玩家的选择 = ''
    while 玩家的选择 != '1' and 玩家的选择 != '2':
        print('押大还是小?(1-小,2-大)')
        玩家的选择 = input()
    
    if 玩家的选择 == '1':
        return '小'
    else:
        return '大'

def 比大小(玩家):
    print('“下好离手!”庄家潇洒地抓起三粒骰子,装进骰盅,摇动了起来。。。')
    time.sleep(2)
    print('只听骰子在骰盅里飞快地转动着,发出清脆的响声。。。')
    time.sleep(2)
    print('“啪”的一声,庄家把骰盅倒扣在桌面上,小心翼翼地打开了。。。')
    time.sleep(2)

    骰子1 = random.randint(1, 6)
    print(骰子1, end=' ')
    time.sleep(2)

    骰子2 = random.randint(1, 6)
    print(骰子2, end=' ')
    time.sleep(2)

    骰子3 = random.randint(1, 6)
    print(骰子3, end=' ')
    time.sleep(2)

    结果 = 骰子1 + 骰子2 + 骰子3

    if 结果 > 10:
        结果 = '大'
    else:
        结果 = '小'

    print(结果 + '!')
    time.sleep(2)

    if 玩家 == 结果:
        print('你赢了!恭喜!')
    else:
        print('你输了!真遗憾')

    time.sleep(2)

背景介绍()

继续玩 = 'yes'
while 继续玩 == 'yes' or 继续玩 == 'y':
    
    玩家的选择 = 下注()

    比大小(玩家的选择)

    print('还要继续吗?(yes-是,no-否)')
    继续玩 = input()
    

四、代码详细分析

1. time.sleep()函数

首先我们想要实现一个叙事节奏的效果。

大家无论是看电影还是玩游戏,接收信息都是有一定节奏的。想象一下,如果所有文字、画面等信息不加停顿地在同一时间展现出来,我们的眼睛一定是应顾不暇的。这个时候,我们就需要一个必要的暂停,让自己的大脑为接收信息做好准备。同时,有节奏的叙事,也能够营造出一种身临其境的效果。

为了实现这个“暂停”,我们用到了Python里time模块的sleep()函数。在括号里填上数字(整数或小数都可以),Python运行到这条命令时,就会暂停xx秒,然后再去执行后面的语句。当然,为了使用sleep()函数,我们要在使用它之前的任何位置先引用time模块(import time)。

以下面本课的部分代码为例,程序每次打印一行文字,都会暂停2秒,仿佛是在等你阅读完上面的信息后,再继续打印后面的内容。由此,我们呈现了一个讲故事的效果。

import time
print('此时已是后半夜,喝得醉醺醺的你,蹒跚着走在这条黑漆漆的小路上。')
time.sleep(2)
print('道路的尽头仍然亮着一间瓦房,里面传出的呼喊声此起彼伏。')
time.sleep(2)
print('“原来是家赌场。”你一边心里想着,一边走了进去。')
time.sleep(2)
print('庄家满脸堆着笑,热情地招呼你过去:“来来来,这边有空位!”')
time.sleep(2)
print('你神情恍惚地抓了张椅子,在赌桌旁坐下。')
time.sleep(2)

在另一部分代码里,我们结合上节课说过的print(’ ‘, end=’ ')方法,模拟出骰子一个一个开出数字的紧张效果。

骰子1 = random.randint(1, 6)
print(骰子1, end=' ')
time.sleep(2)

骰子2 = random.randint(1, 6)
print(骰子2, end=' ')
time.sleep(2)

骰子3 = random.randint(1, 6)
print(骰子3, end=' ')
time.sleep(2)

大家可以动手实验一下,感受一下这种节奏叙事的感觉。

2. 自定义函数

我们在前面已经了解并使用了不少Python系统自带的函数,比如print(),input(),int(),str(),还有引入的random模块里的randint()函数,time模块里的sleep()函数,等等。如果没有这些现成的函数,我们可以说很难实现哪怕输入输出这样的基本功能。

但其实函数的概念也并不复杂。函数只不过是把执行某个特定的功能的一段代码封装起来,起个名字。这样当我们不论什么时候需要用到这个特定功能的时候,只要喊一声这个函数的名字,Python就自动会为我们执行函数里已经写好的代码。(实际上,你可以把函数名也看成是一个变量,里面存放了一段代码。)

当然,你也可以不使用函数(比如本课的小游戏,函数只在一个地方被调用),但使用函数的好处是显而易见的:

  1. 节省了大量重复代码。使用函数,你就可以在任何时候调用它,而不用一遍遍地重复书写相同或类似的代码。
  2. 维护更加容易。当我们想要修改函数的功能、增减一些参数,又或是我们发现函数有错误,只需要在函数的代码块里修改,而程序里调用函数的地方都会发生改变。

定义一个函数

我们刚才说过,函数和变量一样,或者说函数就是一种对象类型(Object)的变量,只是函数里保存的是一段代码,所以函数也必须要向变量那样先定义,再使用。所以习惯上,我们会把函数写在程序的开始。当Python运行时,看到定义函数的部分,会先跳过函数代码块,继续执行后面的语句。相当于我们告诉电脑“先记着有这么个函数,等下我喊到名字的时候再运行”。

定义一个函数需要使用def关键字,格式为def 函数名(): 。注意后面的括号和冒号都是英文半角符号。括号里可以为空,也可以写入任意变量名(我们等下就会介绍局部变量以及形参的概念)。然后换行,缩进写出函数里的代码。

而调用一个函数的方式十分简单,只要在需要的地方写出函数名,记得跟上一对半角圆括号“()”就可以了。根据函数的要求,括号里可以写上函数需要的参数,或什么也不用提供。

当程序执行完函数内部的代码后,可以使用return关键字将一些数据返回给主程序,但也可以不返回任何数据,只是执行代码。在一个函数内部,当程序运行到return的时候,不管return后面还有多少代码,都自动跳出当前函数代码块,回到主程序继续执行函数后面的代码。而主程序如果需要接收函数的返回值,则最好使用赋值语句“=”将返回值保存在变量里。

本例中的自定义函数

在本例中,我们自定义了三个函数:背景介绍(),下注(),比大小()。实现的功能如下:

  1. 背景介绍():在程序的开始,通过print()和time.sleep()函数交替运行,在屏幕上打印出简单地背景信息,营造出一种故事氛围。
  2. 下注():通过调用这个函数,使用input()从玩家这里得到一个选择(1代表“小”或2代表“大”),然后返回给程序。
  3. 比大小():这个函数又提供了两个功能,
    1). 模拟掷骰子的过程,包括使用print()和time.sleep()函数,模拟骰子逐个开出结果的场景。
    2). 将三个骰子的结果相加,并与玩家的选择进行比较,判断胜负。

其中背景介绍()比大小() 都没有返回结果,换句话说,他们都是在调用函数的时候把功能实现完毕了(背景介绍() 是纯粹的文字输出,比大小() 也是在函数内部完成判断并显示胜负),不需要主程序接手。而下注() 这个函数根据用户的输入返回了一个中文字符串,如果用户输入“1”,则返回“小”,输入“2”,则返回“大”。

	if 玩家的选择 == '1':
	    return '小'
	else:
	    return '大'

同时,为了保证玩家只有正确输入“1”或“2”才能继续,我们在函数代码块里使用了while循环语句。如果input()得到的不是“1”或“2”,程序将一直卡在这里。

	while 玩家的选择 != '1' and 玩家的选择 != '2':
	    print('押大还是小?(1-小,2-大)')
	    玩家的选择 = input()

然后函数返回的这个字符串又被赋值到主程序的变量里,注意:我故意在主程序和函数中使用了同名的变量 “玩家的选择”,但其实他们是不同的变量(全局变量与局部变量的区别)。

    玩家的选择 = 下注()

而这个变量又被交给自定义函数比大小(),代入函数去进行比较,判断胜负。

    比大小(玩家的选择)

全局变量与局部变量

刚才提到我在主程序与函数中使用了同名变量 “玩家的选择”,但在这个例子里它们互不相干,仅仅名字相同。

主程序里声明的变量是全局变量(Python省去了变量声明关键字,当第一次使用等号“=”赋值或传参的时候,Python根据赋值的类型自动创建变量)。函数中声明的变量称为局部变量。

当程序运行时,Python创建变量,当程序结束时,变量自动从内存中消失。而函数实际上就是一段子程序,自然也遵循这个规则。当函数被调用时,其中的变量被创建,而当函数运行结束或return返回主程序后,函数关闭,其中的变量也就消失了。

局部变量只能在其被创建的地方(当前函数代码块内)被调用,主程序或其他函数无法调用。而全局变量却可以在程序的任何地方被调用,也包括函数内的代码块。只不过,在函数里却无法修改全局变量的值。因为想要修改变量的值,等于使用等号“=”重新赋值这个变量,而一旦看见赋值语句,Python就会认为我们想在函数里创建一个同名的局部变量,从而就不会再调用主程序中的全局变量了。

可以粗略地打个比方,美国(主程序)有宪法(全局变量),在美国的任何地方以及每个州都要遵守该宪法(调用全局变量),但只有中央政府有权利修改宪法,地方政府无权修改。而在每个州内又可以有自己的法律(局部变量),而一旦有冲突(同名),根据区域自治的原则,每个州会优先遵守本州的法律。

实参与形参

前面讲到函数其实就是存在于主程序内部的一个子程序,颇有种“国家与地方“、“城中城”的感觉。又因为全局变量与局部变量的作用域限制,“城”内外的数据与信息传递存在一定的限制,在使用中颇为不便,于是Python提供了一些专门用于传递这种信息的方法。

  1. 城内(函数)向城外(主程序):我们刚刚说过的return关键字就用于丛函数内部向主程序传递数据,中文叫做“函数返回值”。当然,我们也说过,函数可以返回任何信息,也可以什么都不返回(只有当主程序不接收或不使用函数返回值时)。
  2. 城外(主程序)向城内(函数):函数可以直接调用主程序里的全局变量,这样看来,似乎不存在问题。尤其在我们使用一些常量(默认不改变赋值的变量)时,直接调用最为省事。但是这样做存在两个隐患:
    1). 我们要时时注意主程序里的全局变量有没有发生改变。因为一旦变量的值或类型发生变化,当函数再调用该变量时,就会产生错误。想象一下,某个全局变量a的初始值是数字123,而函数的功能是调用该变量进行数学运算,但是如果主程序里将变量a改变了用途,存入了字符串‘abc’,函数再调用该变量的时候就无法进行数学运算,自然会报错了。
    2). 当其他全局变量需要进行同样的函数操作时,要在函数体里再写一遍同样的代码。

为了解决上面提到的问题,Python提供了传参的方法。参考本例中 比大小() 这个自定义函数。在括号内定义了一个变量“玩家”。这也是局部变量的一种定义方法,即在定义函数的时候,同时预声明了一些局部变量,也叫做形参(形式上的参数)。

def 比大小(玩家):
    # 省略部分代码
    if 玩家 == 结果:
        print('你赢了!恭喜!')
    else:
        print('你输了!真遗憾')

这些局部变量在函数未被调用时,是不存在的(只是预声明,占个位置,所以叫“形式上的参数”)。而当函数被调用时,函数会向主程序要求给这个局部变量赋值(传参)。所以主程序在调用该函数时,要在括号里写明传递给函数的这个局部变量的内容(实参)。所以在主程序看来,这个参数(局部变量)有了实际的内容,所以被称为“实际的参数”。

    比大小(玩家的选择)

本例中,主程序将全局变量“玩家的选择”的值传递给函数比大小(),并赋值给函数的局部变量“玩家”。

需要注意的是,传参的过程是变量赋值给变量的过程(形参 = 实参,所以后面的课程里讲到列表的传参时会有需要特别注意的地方,这里先卖个关子),但变量的数据类型不会发生变化。主程序传了一个整数,接收的局部变量就是整数型(int),传了一个字符串,那接收的局部变量就是字符串型(str)。试着运行以下代码,type()函数用于查看变量的类型。
在这里插入图片描述

3. 布尔型变量与逻辑运算

布尔型变量

我们在第1课就介绍了比较运算符,也是数学运算的一种。但是比较运算符得到的结果只有两种:True或者False,也就是“真”或“假”。而这种非真既假的值也有一个名字,叫做布尔值,用于存储布尔值的变量叫做布尔型变量(bool)。参考以下例子:
在这里插入图片描述
上面的例子也符合我们的常识与认知(1<2必然是真的),而这种二元的概念在我们生活中也随处可见,比如灯的开关,on/off,买与卖,看涨或看跌,等等。所以在程序中也会经常用到。

while循环与if判断语句的原理也是查看跟在后面的表达式的结果是真还是假,简单说,True就继续,False就不符合(在if判断语句里就转到else)。

在Python中,所有其他数据类型都可以自动转换为布尔值,规则也很简单:

  1. 数字0,空的字符串、容器类(列表、集合、元组、字典等)、对象、类,None等等都是False.
  2. 除了上面的值,其他都是True.

逻辑运算

然而世界并不是简单的一分为二、非黑即白,我们需要结合多个条件来得出最终的判断。这些条件可以是同一类型的,比如想知道班级里考试及格(成绩>60分)但是又不够拔尖(成绩<90分)的学生有多少;也可以是不同类型的,比如想知道考试及格(成绩>60分)又会打篮球(篮球技能为True)的学生有多少。譬如这种“既…又…”、“不是…而是…”的结构,在程序的世界里有一个专门的概念,叫做逻辑运算。

和数学运算加减乘除一样,逻辑运算也有自己的运算符,分别是:与(and)、或(or)、非(not)。具体运算规则如下表:

条件A逻辑运算符条件B结果
TrueandTrueTrue
TrueandFalseFalse
FalseandTrueFalse
FalseandFalseFalse
TrueorTrueTrue
TrueorFalseTrue
FalseorTrueTrue
FalseorFalseFalse

简单来说,只要and两边有一个条件不成立(为False),整个and运算的结果就是False;而只要or两边有一个条件成立(为True),整个or运算的结果就是True。

而“非”运算符not更加简单,只作用于一个条件,对其取反。not True自然是False,not False自然为True。

同我们知道的“先乘除后加减”一样,逻辑运算也有运算优先级,not > and > or。

False or True and not False
相当于
False or (True and (not False))
运算的结果为True.

回到本课的例子里,我们在获取玩家选择的时候使用了一个while循环,并判断用户是否正确输入了“1”或者“2”。

def 下注():
    玩家的选择 = ''
    while 玩家的选择 != '1' and 玩家的选择 != '2':
        print('押大还是小?(1-小,2-大)')
        玩家的选择 = input()

这里的意思现在来看就很简单了:如果玩家输入的字符既不是“1”也(and)不是“2”,整个while的条件为True,就继续执行循环,一旦玩家输入正确,则跳出循环。


总结与思考

这节课的例子虽然简单,但也比较有趣,尤其是模拟了开骰子的那种身临其境的紧张感。希望大家都可以体会一下。

也由于本例较为简单,自定义函数仅仅被调用了一次,所以不使用函数也可以完成本例的功能,只要把函数里的代码块挪到主程序调用的位置即可。但是随着以后的程序渐渐变得复杂,涉及到某些特定功能的重复使用,为了使代码更加简洁,问哥还是推荐大家都能掌握函数的用法,并尽可能地在程序中使用函数。

程序虽小,功能不少,以后我们也会不断复用本课里学到的知识点(问哥也不会再对同样的知识点这么啰嗦了:P)。为完成更加复杂的游戏程序,一起加油吧!

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

请叫我问哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值