游戏规则:
产生一个随机的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 很适合你。
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、需要判断用户输入是否合法,如果不是四个数字,则不记录次数,否则记录
那么,我们需要定义一个变量,用来记录次数,还有一个变量,用来记录用户输入信息。并根据题意设置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、先实现结果反馈,如果猜中了,返回次数信息,未猜中,则反馈失败。
if guess == num:
print('你用了 {} 次才猜对哦。'.format(t))
else:
print('你已经使用完 12 次机会了,很遗憾你没能猜中。')
应该没有小伙伴觉得上边的代码难以理解吧,如果还是有小伙伴不理解,还是请在评论区扣我。
很好,我们现在开始进行数字正确与否的判定。先实现第一个判定 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的判断,需要跳过判定A已记录的位置
for i in range(length):
if i not in a:
b += [] # 这里的判定B还没实现,小伙伴们先仔细想一想哦
在跳过判定A的位置后,我们剩余的位置的数字需要全部挑选出来,然后判断某个位置的数字是否存在于剩余数字中,如果存在,也记录这个数字的位置,注意,这里记录的是数字的位置,不是当前数字的位置哦,为什么呢?小伙伴们可以告诉我答案吗?
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的记录
如果数字存在于剩余数字 char 中,我们要求出这个字符所在的索引,即对应于 less 中的数字,所以最后的代码也很简单。
if guess[i] in char:
b.append(less[char.index(guess[i])])
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 游戏的内核。不过由于老顾还没开始学习 pygame,这次就用文字版代替了啊,游戏界面就在开发环境里,当然,你要是弄到命令行里也可以的。
我们知道,每次我们在移动数字以后,会随机出现一个1或者2的新数字,出现位置也是随机的,那么随机函数包还是需要引入的。
import random
同样,我们也知道 2048 这个游戏,其实内容很少,就是一个 4 * 4 的矩形区域,然后数字在里面来回的倒腾。
row = 4
dp = [[0 for _ in range(row)] for _ in range(row)] # 初始化二维列表
由于我们没有图形界面,所以,字符输出的时候,我们需要给每个数字定宽并右对齐。
wid = 7
嗯,分数才是激励游戏进程的正反馈,没有分数的游戏是没有灵魂的。
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')
我们值需要定义四个方向,以及跳出游戏的按键就好,这里老顾用 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
额。。。。直接写完输入后才发现,我们好像少了个循环,游戏又不是只按一次键就完成,所以来个死循环吧。
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
我们都知道合并规则,在输入的方向上,所有该方向的行或者列,相同的数字合并成一个更大的数,一共四个方向,每个方向都需要能合并数字。
小思考:我们需要写几次合并规则?小伙伴们可以仔细想一想哦。
其实,只需要写一次合并判定就可以了,我们只写行合并,且只实现方向向左的合并规则。
实现方法也很简单,把行中的所有的零都剔除,剩余的数字就挨住了,然后循环判断相邻的两个数字是否相同,相同就合并,被合并的位置改成零,避免与更后边的数发生多次合并。
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)
合并判定已经写好了,小伙伴们有没有想到,老顾为什么说,合并判定只需要写一次呢?想到的小伙伴们,在评论区秀下自己!
使用矩阵旋转,不管我们选择什么方向,都将矩阵旋转成左合并的规则就可以了
小伙伴们,有没有答对!答对的小伙伴在评论区打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]
玩过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 # 如果状态一致,则从新输入移动方向
这是应有之意,没有新的数字,进程就停顿在这里了。
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游戏好像也不复杂啊。没有理解透彻的,还有疑问的小伙伴可以在评论区扣我哦。
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])
这次我们用了两个小游戏来熟悉并扩展了一些见世面,如果小伙伴还有什么想了解的,或者有什么好主意,老顾可以安排,一起创作一篇新的文章哦。