2048这个游戏相信大家都不陌生吧。
需要控制所有方块向上下左右的某一方向运动,方向上两个相同数字合并会相加,每次移动后在空白处生成一个2或者4,最终得到一个“2048”的方块就算胜利了。
虽然简单但却虐心,今天我们就来分析这个游戏的算法原理,并用python把这个游戏写出来。游戏分析的思路就是给游戏来一个二向箔,降维打击,如何做,可以往下看。
首先把游戏4*4的方格,称之为棋盘,然后忽略棋盘的背景和颜色,使之变成4*4的田字格,田字格里是对应的数字,空白的方格用0填充。
图变成了一个4*4的方格,接下来去除方格,把每一行提取出来写到python的列表里,比如第一行变成 [2, 0, 2, 0], 那么这个4*4的方格,就变成了一个4*4的二维矩阵。
抽象成python的数据格式之后,我们看数字移动和数字加减的操作。以向左移动为例,移动的结果,第一行会变成[4, 0, 0, 0 ], 第三行会变成[8, 0, 0, 0],即
可以想象每一行计算的方法是一致的,只要搞定了一行数字的移动算法,那么整体4行就是把这个算法执行4遍即可。所以我们就完成了初步的降维,把4*4的计算,变成了4*1的计算。
接下来以第一行的数字的移动为例,介绍数字该怎么移动。
还看移动之前该矩阵的第一行 [2, 0, 2, 0], 向左移动时,需要把不为零的数字,一起向左移动,第一位的2不需要移动,第二位是0,不看,第三位的2,需要往左一位。那么我们换个思路,2向左移动,是不是可以看成是第二位的0向后移动,如果我们把第二位的0先删掉,[2, 0, 2, 0] 变成 [2, 2, 0], 然后再把0在最后添加进去,[2, 2, 0]变成[2, 2, 0, 0],这样2就向左移动了,神奇不神奇?
那完成了移动,怎么完成相同的数字相加呢?
其实只要从行的第一位开始,比较它和它后边的那一位,如果一样,就把它乘以2,然后把它后边的那一位删掉,最后在行的末尾添加一个0,保证矩阵的shape一直是 4*4。
ok,到这里完成了一行的向左移并相加的算法,那么四行的算法是一致的,也就完成了整个4*4矩阵左移并相加的操作。
那么问题来了,向右移该怎么做呢?
其实右移和左移的操作很相似,只在行的方位上移动数字,行与行之间没有什么直接操作,那么在右移时,先把每一行逆序一下,也就是 [2, 0, 2, 0]变成 [0, 2, 0, 2], 再做左移的操作,完成之后,把结果再逆序一下,是不是就ok了呢?
再问你一遍,神奇不神奇?
更神奇的还在后边呢,目前完成了左移和右移的操作,那么上移和下移呢?接下来我们要给这个游戏来第二次降维打击。
这里要用到一个我们学过的线代的知识,矩阵的转置,行转列,列转行。
细心观察之后,你会发现,原本矩阵数字上移的操作,经过转置之后就变成了左移的操作。又可以用我们之前的算法了。(鼓掌)
那么上移的操作就变成了 转置-->左移-->再转置。
那么下移呢,聪明的你肯定已经猜出来了。
没错下移就是 转置-->右移-->再转置。
ok,至此,2048游戏核心逻辑,分析完毕,来个总结:
1. 以左移为基础,把4行的操作看成1行的操作。
2. 左移的操作,先把0全部移到行的末尾
3. 然后从行第一位开始,判断与后一位是否相等且不等0,若相等该位乘以2,删除后一位,末尾添加0
4. 右移操作是 行逆序--左移--再逆序
5. 上移操作是 矩阵转置--左移--再转置
6. 下移操作是 矩阵转置--右移--再转置
上代码:
class AlgorithmLogic:
def __init__(self, matrix_shape=(4, 4)):
self.matrix = [[0 for i in range(matrix_shape[0])]
for i in range(matrix_shape[1])]
self.generate_new_num()
self.generate_new_num()
def zero_to_end(self, row):
# 倒着遍历
for i in range(len(row) - 1, -1, -1):
if row[i] == 0:
row.pop(i)
row.append(0)
def merge(self, row):
# 移动并合并
self.zero_to_end(row)
for i in range(len(row) - 1):
if row[i] == 0:
break
if row[i] == row[i + 1]:
row[i] *= 2
row.pop(i + 1)
row.append(0)
def left(self):
for row in self.matrix:
self.merge(row)
def right(self):
for row in self.matrix:
row.reverse()
self.merge(row)
row.reverse()
def matrix_transpose(self):
# 矩阵转置
for col_index in range(1, len(self.matrix)):
for row_index in range(col_index, len(self.matrix)):
self.matrix[row_index][col_index - 1], self.matrix[
col_index - 1][row_index] = self.matrix[
col_index -
1][row_index], self.matrix[row_index][col_index - 1]
def up(self):
self.matrix_transpose()
self.left()
self.matrix_transpose()
def down(self):
self.matrix_transpose()
self.right()
self.matrix_transpose()
def move(self, direction):
dir_dict = {
'left': self.left,
'right': self.right,
'up': self.up,
'down': self.down
}
func = dir_dict.get(direction)
if func:
func()
def get_empty_position(self):
empty_position = []
for row_index in range(len(self.matrix)):
for col_index in range(len(self.matrix[row_index])):
if self.matrix[row_index][col_index] == 0:
empty_position.append((row_index, col_index))
return empty_position
def generate_new_num(self):
self.empty_position = self.get_empty_position()
if not self.empty_position:
return False
row_index, col_index = random.choice(self.empty_position)
self.matrix[row_index][col_index] = 4 if random.randint(1,
10) == 1 else 2
self.empty_position.remove((row_index, col_index))
return True
def is_game_win(self):
for r in self.matrix:
if 2048 in r:
return True
return False
def is_game_over(self):
for r in range(len(self.matrix)):
for c in range(len(self.matrix[r]) - 1):
# 判断是否还可以走
if self.empty_position:
return False
# 判断是否还可以相加
if self.matrix[r][c] == self.matrix[r][
c + 1] or self.matrix[c][r] == self.matrix[c + 1][r]:
return False
return True
这里展示算法的主要逻辑代码
完整的代码移至打代码的shy--python写游戏系列github.com
初来乍到,请多关照。