当前位置: 首页 > news >正文

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

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:
        continue
    if len(set(guess) - set(s)) > 0:
        continue
    t += 1
    if 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:
        continue
    if len(set(guess) - set(s)) > 0:
        continue
    t += 1
    a,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:
        continue
    if inp == 'q':
        break
    if 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:
        continue
    if inp == 'q':
        break
    if inp not in 'asdw':
        continue

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

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

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

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

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

    for i in range(row):
        while dp[i].count(0) > 0:
            dp[i].pop(dp[i].index(0)) # 剔除 0
        for j in range(len(dp[i]) - 1):
            if dp[i][j] == dp[i][j + 1]:
                dp[i][j] *= 2 # 合并
                dp[i][j + 1] = 0 # 被合并的位置改成 0
        while dp[i].count(0) > 0: # 剔除合并后产生的 0
            dp[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  # 如果任意一个位置的数字不一样,则判定有合并或移动
                break
        if not isSame:
            break

    if 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 sys

row,wid = 4,7

dp = [[0 for _ in range(row)] for _ in range(row)]
score = 0

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')
    sys.stdout.flush() # 为了避免输入位置总是不在正确的位置,强制刷新输出缓存
    inp = input('请选择方向(ASDW)Q退出:').strip().lower()
    if len(inp) != 1:
        continue
    if inp == 'q':
        break
    if inp not in 'asdw':
        continue
    if 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] *= 2
                dp[i][j + 1] = 0
        while dp[i].count(0) > 0:
            dp[i].pop(dp[i].index(0))
        while len(dp[i]) < row:
            dp[i].append(0)
    
    isSame = True
    for i in range(row):
        for j in range(row):
            if comp[i][j] != dp[i][j]:
                isSame = False
                break
        if not isSame:
            break
    
    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]

    free = [(m,n) for m in range(row) for n in range(row) if dp[m][n] == 0]
    if len(free) == 0: # 没有剩余空位,游戏失败
        break

    if isSame:  # 自己发现个小bug,这个判定要放到失败判定之后。
        continue

    selected = random.choice(free)
    dp[selected[0]][selected[1]] = random.randint(1,2)
    score = sum([sum(r) for r in dp])

小结

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

相关文章:

  • async与await异步编程
  • MyBatisPlus+SpringBoot实现乐观锁功能
  • 【Vue全家桶】带你全面了解通过Vue CLI初始化Vue项目
  • CSS 属性计算过程
  • 一文了解Jackson注解@JsonFormat及失效解决
  • 全自动托盘四向穿梭车|拥有输送系统提升机AGV的托盘四向穿梭车立体库的软硬件配置系统
  • “你要多弄弄算法”
  • 【Android -- 开发工具】Xshell 6 安装和使用教程
  • Tomcat And Servlet (1)
  • C++ 学习笔记(十)(继承、抽象篇)
  • 【蓝桥杯集训·每日一题】 AcWing 3996. 涂色
  • 【Linux】基础IO(一) :文件描述符,文件流指针,重定向
  • 网络安全文章汇总导航(持续更新)
  • 菜鸟刷题Day1
  • C语言通讯录应用程序:从设计到实现
  • ----------
  • #Java异常处理
  • axios请求、和返回数据拦截,统一请求报错提示_012
  • ES2017异步函数现已正式可用
  • ES6 ...操作符
  • Git的一些常用操作
  • idea + plantuml 画流程图
  • nfs客户端进程变D,延伸linux的lock
  • TiDB 源码阅读系列文章(十)Chunk 和执行框架简介
  • Vue 动态创建 component
  • vue:响应原理
  • Web Storage相关
  • 产品三维模型在线预览
  • 初识 webpack
  • 分布式熔断降级平台aegis
  • 开发了一款写作软件(OSX,Windows),附带Electron开发指南
  • 前端之React实战:创建跨平台的项目架构
  • 一加3T解锁OEM、刷入TWRP、第三方ROM以及ROOT
  • 一些css基础学习笔记
  • Spring Batch JSON 支持
  • 好程序员大数据教程Hadoop全分布安装(非HA)
  • 数据可视化之下发图实践
  • 直播平台建设千万不要忘记流媒体服务器的存在 ...
  • # 透过事物看本质的能力怎么培养?
  • #Linux(Source Insight安装及工程建立)
  • $(function(){})与(function($){....})(jQuery)的区别
  • $GOPATH/go.mod exists but should not goland
  • (2)nginx 安装、启停
  • (a /b)*c的值
  • (二)c52学习之旅-简单了解单片机
  • (附源码)spring boot公选课在线选课系统 毕业设计 142011
  • (附源码)springboot宠物管理系统 毕业设计 121654
  • (附源码)ssm户外用品商城 毕业设计 112346
  • (附源码)小程序 交通违法举报系统 毕业设计 242045
  • (介绍与使用)物联网NodeMCUESP8266(ESP-12F)连接新版onenet mqtt协议实现上传数据(温湿度)和下发指令(控制LED灯)
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理 第13章 项目资源管理(七)
  • (已解决)报错:Could not load the Qt platform plugin “xcb“
  • (转)全文检索技术学习(三)——Lucene支持中文分词
  • .FileZilla的使用和主动模式被动模式介绍
  • .net core 6 集成和使用 mongodb