python 基础系列篇:四、编写两个简单的小游戏(猜数字及2048)

news/2025/2/11 16:02:09/

python 基础系列篇:四、编写两个简单的小游戏(猜数字及2048)

  • 猜数字游戏
    • 游戏进程示例
    • 需求分析并逐步实现
      • 1、使用随机函数包 random
      • 2、记录用户输入
      • 3、提前做好的结果反馈
      • 4、判定A的实现
      • 5、判定B的范围
      • 6、判定B的判定内容
      • 7、判定B的实现
      • 8、用户输入信息的反馈及完成
  • 2048 游戏
    • 游戏进程示例
    • 需求分析并逐步实现
      • 1、使用随机函数包
      • 2、使用二维列表
      • 3、定义每个数字显示的宽度
      • 4、使用变量记录当前分数
      • 5、游戏初始,我们应该有一些数字
      • 6、游戏界面呈现
      • 7、接收用户输入,非法的内容跳过从新输入
      • 8、貌似我们少了个循环控制?
      • 9、如何判断数字合并呢?
      • 10、使用矩阵旋转
      • 11、追加未能合并判定
      • 12、合并或移动后,出现新的数字
      • 13、完整的代码呈现
  • 小结

猜数字游戏

游戏规则:
产生一个随机的4位数,可能会有前置0,用户每输入一次4位数,记录次数加1,并返回猜测结果,位置正确数字正确的为一种,输出一个A,数字正确位置不正确的为另一种,输出一个B,限定12次(含)以内猜出数字为胜利,否则视为挑战失败。

游戏进程示例

挑战示例1:
请输入 4 位数字,可以前置 0 :0123
B
请输入 4 位数字,可以前置 0 :4567
AB
请输入 4 位数字,可以前置 0 :4689
BB
请输入 4 位数字,可以前置 0 :8967
B
请输入 4 位数字,可以前置 0 :3576
A
请输入 4 位数字,可以前置 0 :2589
AAB
请输入 4 位数字,可以前置 0 :2549
AABB
请输入 4 位数字,可以前置 0 :2594
AAAA
你用了 8 次才猜对哦。

挑战示例2:
请输入 4 位数字,可以前置 0 :0123
AA
请输入 4 位数字,可以前置 0 :0145
A
请输入 4 位数字,可以前置 0 :6127
AA
请输入 4 位数字,可以前置 0 :6183
AAA
请输入 4 位数字,可以前置 0 :6193
AAA
请输入 4 位数字,可以前置 0 :6113
AAA
请输入 4 位数字,可以前置 0 :6133
AAA
请输入 4 位数字,可以前置 0 :6163
AAAA
你用了 8 次才猜对哦。

相信小伙伴们看过描述,以及两个示例之后,应该明白游戏规则了吧?如果不明白游戏规则的小伙伴,请在评论区扣我哦。

好了,我们言归正传,开始分析一下,我们实现这个小游戏,需要做些什么工作。

需求分析并逐步实现

1、使用随机函数包 random

1、我们需要一个随机函数库,random 很适合你。

import random
s = '0123456789'
length = 4
num = ''.join([random.choice(s) for _ in range(length)])
print(num)
---
7431 # 第一次结果
6357 # 第二次结果
6421 # 第三次结果

很好,随机产生的数字完成了。通过 random.choice 随机从我们的 s 字符串里抽取一个数字的方式,并用 join 方式组成一个数字字符串。

2、记录用户输入

2、需要判断用户输入是否合法,如果不是四个数字,则不记录次数,否则记录

那么,我们需要定义一个变量,用来记录次数,还有一个变量,用来记录用户输入信息。并根据题意设置12次的时候跳出循环。

t = 0 # 计次
guess = ''
while guess != num:guess = input(f'请输入 {length} 位数字,可以前置 0 :')if len(guess) != length:continueif len(set(guess) - set(s)) > 0:continuet += 1if t == 12:break

3、提前做好的结果反馈

3、先实现结果反馈,如果猜中了,返回次数信息,未猜中,则反馈失败。

if guess == num:print('你用了 {} 次才猜对哦。'.format(t))
else:print('你已经使用完 12 次机会了,很遗憾你没能猜中。')

应该没有小伙伴觉得上边的代码难以理解吧,如果还是有小伙伴不理解,还是请在评论区扣我。

很好,我们现在开始进行数字正确与否的判定。先实现第一个判定 A。

4、判定A的实现

4、数字正确,位置正确的判定 A

我们把下边的代码插入到 t += 1 后边

    a,b = [],[] # 用来记录判定A及判定B的数据for i in range(length):if num[i] == guess[i]: # 如果数字和位置一致,则记录到判定A中a.append(i)

小伙伴们可以想一想,为什么我这里记录的是位置信息哦。

没错,这个位置的数字就不需要再进行判定了。所以我们在进行判定B的判断时,需要跳过这些位置的数字。

5、判定B的范围

5、进行判定B的判断,需要跳过判定A已记录的位置

    for i in range(length):if i not in a:b += [] # 这里的判定B还没实现,小伙伴们先仔细想一想哦

在跳过判定A的位置后,我们剩余的位置的数字需要全部挑选出来,然后判断某个位置的数字是否存在于剩余数字中,如果存在,也记录这个数字的位置,注意,这里记录的是数字的位置,不是当前数字的位置哦,为什么呢?小伙伴们可以告诉我答案吗?

6、判定B的判定内容

6、判定B记录内容解析

假设数字为 1234,用户输入为0123。

那么第一次判定应该是 0 ,这个数字不存在于 1234 中。

第二次判断的数字是1,这个数字存在于1234中。记录到判定B的时候,我们需要把1这个数字所在的位置记录,即索引0,而不是用户输入的数字的索引1。

原因很简单,表示需要猜测的这个数字中索引1的位置已经被占用了。

那么,理解了这一点之后,后边的就简单了。把剩余的字符找出来,并同时记录剩余字符的索引位置。

            less = [v for v in range(length) if v not in a and v not in b] # 记录剩余数字的索引char = [num[v] for v in range(length) if v not in a and v not in b] # 记录剩余数字的内容

7、判定B的实现

7、最后实现判定B的记录

如果数字存在于剩余数字 char 中,我们要求出这个字符所在的索引,即对应于 less 中的数字,所以最后的代码也很简单。

            if guess[i] in char:b.append(less[char.index(guess[i])])

8、用户输入信息的反馈及完成

8、添加最终的反馈信息输出,完成游戏,并进行测试

最终完整代码已经拼接出来了,我们来看看完整代码是如何的吧:

import random
s = '0123456789'
length = 4
num = ''.join([random.choice(s) for _ in range(length)])
# print(num) 这里一定要注释掉或删除掉哦,否则就是作弊了呢
t = 0 # 计次
guess = ''
while guess != num:guess = input(f'请输入 {length} 位数字,可以前置 0 :')if len(guess) != length:continueif len(set(guess) - set(s)) > 0:continuet += 1a,b = [],[]for i in range(length):if num[i] == guess[i]:a.append(i)for i in range(length):if i not in a:less = [v for v in range(length) if v not in a and v not in b]char = [num[v] for v in range(length) if v not in a and v not in b]if guess[i] in char:b.append(less[char.index(guess[i])])print(len(a) * 'A' + len(b) * 'B') # 最终的反馈信息在这里哦,就一句话的事if t == 12:break
if guess == num:print('你用了 {} 次才猜对哦。'.format(t))
else:print('你已经使用完 12 次机会了,很遗憾你没能猜中。')

2048 游戏

相信大部分小伙伴应该都在手机上玩过这个游戏了,这次,我们也来自己实现一下 2048 游戏的内核。不过由于老顾还没开始学习 pygame,这次就用文字版代替了啊,游戏界面就在开发环境里,当然,你要是弄到命令行里也可以的。

游戏进程示例

截图如下
在这里插入图片描述
在这里插入图片描述

需求分析并逐步实现

1、使用随机函数包

我们知道,每次我们在移动数字以后,会随机出现一个1或者2的新数字,出现位置也是随机的,那么随机函数包还是需要引入的。

import random

2、使用二维列表

同样,我们也知道 2048 这个游戏,其实内容很少,就是一个 4 * 4 的矩形区域,然后数字在里面来回的倒腾。

row = 4
dp = [[0 for _ in range(row)] for _ in range(row)] # 初始化二维列表

3、定义每个数字显示的宽度

由于我们没有图形界面,所以,字符输出的时候,我们需要给每个数字定宽并右对齐。

wid = 7

4、使用变量记录当前分数

嗯,分数才是激励游戏进程的正反馈,没有分数的游戏是没有灵魂的。

score = 0

5、游戏初始,我们应该有一些数字

大部分游戏,初始就有一到三个数字已经在游戏中出现了,我们也来模拟实现

        for i in range(random.randint(1,3)):free = [(m,n) for m in range(row) for n in range(row) if dp[m][n] == 0]selected = random.choice(free)dp[selected[0]][selected[1]] = random.randint(1,2)score = sum([sum(r) for r in dp])

6、游戏界面呈现

前面做了这么多分析工作,结果我们还看不到自己做的东西,那就不太好了,我们来输出一下数据吧

    print('\n------------------------------')for r in dp:print('\n\n',''.join([str(n).rjust(wid) if n > 0 else '.'.rjust(wid) for n in r]))print('\n\nScore:',score)print('\n')

在这里插入图片描述
很好,有个初始进程了。

7、接收用户输入,非法的内容跳过从新输入

我们值需要定义四个方向,以及跳出游戏的按键就好,这里老顾用 asdw来表示方向 a 为左,d为右,w为上,s为下,q为退出游戏。

    inp = input('请选择方向(ASDW)Q退出:').strip().lower()if len(inp) != 1:continueif inp == 'q':breakif inp not in 'asdw':continue

8、貌似我们少了个循环控制?

额。。。。直接写完输入后才发现,我们好像少了个循环,游戏又不是只按一次键就完成,所以来个死循环吧。

while True:if score == 0:for i in range(random.randint(1,3)):free = [(m,n) for m in range(row) for n in range(row) if dp[m][n] == 0]selected = random.choice(free)dp[selected[0]][selected[1]] = random.randint(1,2)score = sum([sum(r) for r in dp])print('\n------------------------------')for r in dp:print('\n\n',''.join([str(n).rjust(wid) if n > 0 else '.'.rjust(wid) for n in r]))print('\n\nScore:',score)print('\n')inp = input('请选择方向(ASDW)Q退出:').strip().lower()if len(inp) != 1:continueif inp == 'q':breakif inp not in 'asdw':continue

9、如何判断数字合并呢?

我们都知道合并规则,在输入的方向上,所有该方向的行或者列,相同的数字合并成一个更大的数,一共四个方向,每个方向都需要能合并数字。

小思考:我们需要写几次合并规则?小伙伴们可以仔细想一想哦。

其实,只需要写一次合并判定就可以了,我们只写行合并,且只实现方向向左的合并规则。

实现方法也很简单,把行中的所有的零都剔除,剩余的数字就挨住了,然后循环判断相邻的两个数字是否相同,相同就合并,被合并的位置改成零,避免与更后边的数发生多次合并。

    for i in range(row):while dp[i].count(0) > 0:dp[i].pop(dp[i].index(0)) # 剔除 0for j in range(len(dp[i]) - 1):if dp[i][j] == dp[i][j + 1]:dp[i][j] *= 2 # 合并dp[i][j + 1] = 0 # 被合并的位置改成 0while dp[i].count(0) > 0: # 剔除合并后产生的 0dp[i].pop(dp[i].index(0))while len(dp[i]) < 4: # 每行用零补足四个dp[i].append(0)

合并判定已经写好了,小伙伴们有没有想到,老顾为什么说,合并判定只需要写一次呢?想到的小伙伴们,在评论区秀下自己!

10、使用矩阵旋转

使用矩阵旋转,不管我们选择什么方向,都将矩阵旋转成左合并的规则就可以了

小伙伴们,有没有答对!答对的小伙伴在评论区打call哦。

    if inp == 'w':dp[:] = [[dp[m][n] for m in range(row)] for n in range(row)][::-1] # 左旋转 90 度if inp == 's':dp[:] = [[dp[m][n] for m in range(row)][::-1] for n in range(row)] # 右旋转 90 度if inp == 'd':dp[:] = [[dp[n][m] for m in range(row)][::-1] for n in range(row)][::-1] # 旋转 180 度

嗯,这里出现了一个骚操作,推导式且不去管他,dp[:] 是个什么鬼?

这里不得不说元组的强大了,如果等号前边是一个多元素变量,等号后的数据有同等数量的结果,那么就可以这么操作了!

嗯嗯。。。别听老顾说大话,老顾只见过列表这么用。

再回到主题,我们通过旋转,可以都用左合并的方式实现合并判定了,在合并后,再旋转回来就好。

    if inp == 'w':dp[:] = [[dp[m][n] for m in range(row)][::-1] for n in range(row)]if inp == 's':dp[:] = [[dp[m][n] for m in range(row)] for n in range(row)][::-1]if inp == 'd':dp[:] = [[dp[n][m] for m in range(row)][::-1] for n in range(row)][::-1]

11、追加未能合并判定

玩过2048的小伙伴都知道,假如一个方向上无法合并、移动,那么你选择这个方向,也不会出现新的数字,相当于无效的输入。

这个时候,我们就需要记录数字合并判定前的状态,以及合并后的状态是否一致了。

在 python 里,有个 copy 包,可以很方便的复制列表、词典等对象,我们称之为深拷贝。

import copy  # 引入 copy 包,以便使用深拷贝comp = copy.deepcopy(dp) # 合并前追加深拷贝isSame = True  # 合并后,判断状态是否一致for i in range(row):for j in range(row):if comp[i][j] != dp[i][j]:isSame = False  # 如果任意一个位置的数字不一样,则判定有合并或移动breakif not isSame:breakif isSame:continue  # 如果状态一致,则从新输入移动方向

12、合并或移动后,出现新的数字

这是应有之意,没有新的数字,进程就停顿在这里了。

    free = [(m,n) for m in range(row) for n in range(row) if dp[m][n] == 0]if len(free) == 0: # 如果所有的位置都已经有了数字,游戏结束break   selected = random.choice(free)dp[selected[0]][selected[1]] = random.randint(1,2)score = sum([sum(r) for r in dp])

这么看起来,2048游戏好像也不复杂啊。没有理解透彻的,还有疑问的小伙伴可以在评论区扣我哦。

13、完整的代码呈现

import random
import copy
import sysrow,wid = 4,7dp = [[0 for _ in range(row)] for _ in range(row)]
score = 0while True:if score == 0:for i in range(random.randint(1,3)):free = [(m,n) for m in range(row) for n in range(row) if dp[m][n] == 0]selected = random.choice(free)dp[selected[0]][selected[1]] = random.randint(1,2)score = sum([sum(r) for r in dp])print('\n------------------------------')for r in dp:print('\n\n',''.join([str(n).rjust(wid) if n > 0 else '.'.rjust(wid) for n in r]))print('\n\nScore:',score)print('\n')sys.stdout.flush() # 为了避免输入位置总是不在正确的位置,强制刷新输出缓存inp = input('请选择方向(ASDW)Q退出:').strip().lower()if len(inp) != 1:continueif inp == 'q':breakif inp not in 'asdw':continueif inp == 'w':dp[:] = [[dp[m][n] for m in range(row)] for n in range(row)][::-1]if inp == 's':dp[:] = [[dp[m][n] for m in range(row)][::-1] for n in range(row)]if inp == 'd':dp[:] = [[dp[n][m] for m in range(row)][::-1] for n in range(row)][::-1]comp = copy.deepcopy(dp)for i in range(row):while dp[i].count(0) > 0:dp[i].pop(dp[i].index(0))for j in range(len(dp[i]) - 1):if dp[i][j] == dp[i][j + 1]:dp[i][j] *= 2dp[i][j + 1] = 0while dp[i].count(0) > 0:dp[i].pop(dp[i].index(0))while len(dp[i]) < row:dp[i].append(0)isSame = Truefor i in range(row):for j in range(row):if comp[i][j] != dp[i][j]:isSame = Falsebreakif not isSame:breakif inp == 'w':dp[:] = [[dp[m][n] for m in range(row)][::-1] for n in range(row)]if inp == 's':dp[:] = [[dp[m][n] for m in range(row)] for n in range(row)][::-1]if inp == 'd':dp[:] = [[dp[n][m] for m in range(row)][::-1] for n in range(row)][::-1]free = [(m,n) for m in range(row) for n in range(row) if dp[m][n] == 0]if len(free) == 0: # 没有剩余空位,游戏失败breakif isSame:  # 自己发现个小bug,这个判定要放到失败判定之后。continueselected = random.choice(free)dp[selected[0]][selected[1]] = random.randint(1,2)score = sum([sum(r) for r in dp])

小结

这次我们用了两个小游戏来熟悉并扩展了一些见世面,如果小伙伴还有什么想了解的,或者有什么好主意,老顾可以安排,一起创作一篇新的文章哦。


http://www.ppmy.cn/news/34394.html

相关文章

C++ String类总结

头文件 #include <string>构造函数 default (1) basic_string();explicit basic_string (const allocator_type& alloc); copy (2) basic_string (const basic_string& str);basic_string (const basic_string& str, const allocator_type& alloc); su…

如何做好数字化知识管理?

随着信息技术的迅速发展和普及&#xff0c;现代企业已经逐渐进入到数字化时代。数字化建设对于企业来说&#xff0c;既是一种趋势&#xff0c;也是一种必要性。数字化建设的目的是提高企业的效率、降低成本、优化管理和提升企业的核心竞争力。数字化建设可以帮助企业实现数字化…

超级实用,解密云原生监控技术,使用prometheus轻松搞定redis监控

前言 大家好&#xff0c;我是沐风晓月&#xff0c;本文收录于《 prometheus监控系列》 &#xff0c;截止目前prometheus专栏已经更新到第8篇文章。 本文中的是prometheus已经安装好&#xff0c;如果你还未安装&#xff0c;可以参考 prometheus安装及使用入门 若你想监控其他…

Sulfo-Cyanine7.5 azide,Sulfo-Cyanine7.5 N3,磺酸基Cy7.5 叠氮,可用来方便地标记各种生物分子

Sulfo-Cyanine7.5 azide&#xff0c;Sulfo-Cyanine7.5 N3&#xff0c;Sulfo-Cy7.5 N3&#xff0c;磺酸基Cy7.5 叠氮&#xff0c;磺酸基Cyanine7.5 叠氮一.产品规格&#xff1a;1.CAS号&#xff1a;N/A2.分子式&#xff1a;C48H51N6K3O13S43.分子量&#xff1a;1165.514.包装规格…

[SpringBoot] 解决Redis相关问题

缓存穿透缓存击穿缓存雪崩 文章目录1.缓存穿透2.缓存击穿3.缓存雪崩1.缓存穿透 缓存穿透指的是一个缓存系统无法缓存某个查询的数据&#xff0c;从而导致这个查询每一次都要访问数据库。 常见的Redis缓存穿透场景包括&#xff1a; 查询一个不存在的数据&#xff1a;攻击者可…

python例程:五子棋(控制台版)程序

目录《五子棋&#xff08;控制台版&#xff09;》程序使用说明程序示例代码可执行程序及源码下载路径《五子棋&#xff08;控制台版&#xff09;》程序使用说明 在PyCharm中运行《五子棋&#xff08;控制台版&#xff09;》即可进入如图1所示的系统主界面。 图1 游戏主界面 具…

【Linux】进程概念二

文章目录进程概念二1. 进程状态2. 进程状态查看3. 僵尸进程3.1 僵尸进程的危害4. 孤儿进程5. 环境变量5.1 常见环境变量5.2 查看环境变量的方法5.3 测试PATH5.4 环境变量相关的命令5.5 环境变量的组织方式5.6 通过代码获取环境变量6. 程序地址空间7. 进程地址空间8. 扩展8.1 为…

三十七、实战演练之接口自动化平台的文件上传

上传文件功能 上传文件功能主要针对需要测试上传文件的接口。原理是&#xff0c;把要测试上传的文件先上传到测试平台&#xff0c;然后把路径写入 用例中&#xff0c;后台真正测试时再将其进行上传。 一、上传文件模型 在testplans/models.py 模块中编写如下模型&#xff1a;…