2048的游戏规则:简单的移动方向键让数字叠加,并且获得这些数字每次叠加后的得分,当出现2048这个数字时游戏胜利。同时每次移动方向键时,都会在这个4*4的方格矩阵的空白区域随机产生一个数字2或者4,如果方格被数字填满了,那么就GameOver了。
一开始讲解的是控制台输出的版本,包含图形化界面的代码放在文末
本次小游戏采用面向对象的方式实现
代码如下:
from random import randrange
class Game:
# 棋盘
def __init__(self):
self.board_list = [
['', '', '', ''],
['', '', '', ''],
['', '', '', ''],
['', '', '', '']]
# 得分
self.score = 0
f = open("最高分.txt", "r")
a = f.read()
self.best = int(a)
# 空位
self.board_empty = []
def start(self):
"""
游戏开始方法
:return:
"""
self.restart()
while True:
self.print_bora()
code = input("请输入指令>>>:")
if code == 'w':
# 向上
self.move_up()
elif code == 's':
# 向下
self.move_down()
elif code == 'a':
# 向左
self.move_left()
elif code == 'd':
# 向右
self.move_right()
elif code == 'r':
# 重启
self.restart()
self.score = 0
continue
elif code == 'q':
# 退出
exit('退出')
else:
print('你的输入有误,请重新输入')
continue
# 判断游戏是否win赢了
self.Win_game()
# print('You Win!')
# break
# 判断游戏是否输了
if self.Lose_game():
print('You Lose!')
break
self.add_pice()
def Win_game(self):
"""
赢得游戏
:return:
"""
# 判断是否赢了,并且判断是否有空位
self.board_empty = []
for i in range(len(self.board_list)):
for j in range(len(self.board_list[i])):
# if self.board_list[i][j] == 2048:
# return True
if self.board_list[i][j] == '':
self.board_empty.append((i, j))
return False
def Lose_game(self):
"""
输掉游戏
:return:
"""
if not self.board_empty:
# 判断每行每列有没有相等的元素
# 判断每一行
for i in range(len(self.board_list)):
for j in range(len(self.board_list[i]) - 1):
if self.board_list[i][j] == self.board_list[i][j + 1]:
return False
# 判断每一列
# 先右转棋盘
self.turn_right()
for i in range(len(self.board_list)):
for j in range(len(self.board_list[i]) - 1):
if self.board_list[i][j] == self.board_list[i][j + 1]:
self.turn_left()
return False
return True
return False
def add_pice(self):
"""
添加棋子,在空白的位置添加2,4
:return:
"""
# 先随机位置,再随机值
if self.board_empty:
# 提取并删除这个随机位置
p = self.board_empty.pop(randrange(len(self.board_empty)))
# 再随机生成2,4
self.board_list[p[0]][p[1]] = randrange(2, 5, 2)
def restart(self):
# 初始化棋盘
self.board_list = [
['', '', '', ''],
['', '', '', ''],
['', '', '', ''],
['', '', '', '']]
# 随机两个位置
# 随机2,4
while True:
t1 = (randrange(len(self.board_list)), randrange(len(self.board_list[0])))
t2 = (randrange(len(self.board_list)), randrange(len(self.board_list[0])))
if t1 != t2:
break
self.board_list[t1[0]][t1[1]] = randrange(2, 5, 2)
self.board_list[t2[0]][t2[1]] = randrange(2, 5, 2)
def move_up(self):
# 先左转
self.turn_left()
# 再向左操作
self.move_left()
# 再右转
self.turn_right()
def move_down(self):
# 先右转90度
self.turn_right()
# 再向左操作
self.move_left()
# 再转回来
self.turn_left()
def move_left(self):
for i in range(len(self.board_list)):
self.board_list[i] = self.row_left_oper(self.board_list[i])
def move_right(self):
# 先左转
self.turn_left()
# 再向上操作、
self.move_up()
# 再右转
self.turn_right()
def row_left_oper(self, row):
"""
向左把一行列表进行2048操作
:param row:
:return:
"""
temp = []
for item in row:
if item:
temp.append(item)
new_row = []
flag = True # 用于跳过相邻两个相等后的判断
for i in range(len(temp)):
if flag:
if i + 1 < len(temp) and temp[i] == temp[i + 1]:
new_row.append(temp[i] * 2)
self.score += temp[i] * 2
if self.score > self.best:
self.best = self.score
f = open("最高分.txt", "w")
f.write(str(self.best))
f.close()
flag = False
else:
new_row.append(temp[i])
else:
flag = True
n = len(row)
for i in range(n - len(new_row)):
new_row.append('')
return new_row
def turn_right(self):
"""
将棋盘向右旋转90度
:return:
"""
self.board_list = [list(x[::-1]) for x in zip(*self.board_list)]
def turn_left(self):
"""
将棋盘向左旋转90度
:return:
"""
for i in range(3):
self.board_list = [list(x[::-1]) for x in zip(*self.board_list)]
def print_bora(self):
"""
打印棋盘
:return:
"""
print('''SCORE:{} BEST:{}
+-----+-----+-----+-----+
|{:^5}|{:^5}|{:^5}|{:^5}|
+-----+-----+-----+-----+
|{:^5}|{:^5}|{:^5}|{:^5}|
+-----+-----+-----+-----+
|{:^5}|{:^5}|{:^5}|{:^5}|
+-----+-----+-----+-----+
|{:^5}|{:^5}|{:^5}|{:^5}|
+-----+-----+-----+-----+
w(up),s(down),a(left),d(right)
r(restart),q(exit)'''.format(self.score, self.best,
*self.board_list[0],
*self.board_list[1],
*self.board_list[2],
*self.board_list[3]))
if __name__ == '__main__':
game = Game()
game.start()
大致讲解一下实现过程:
1.先需要一个嵌套的4*4列表来存储数据,还需要熟练的使用字符串的格式化输出。
2.有了棋盘之后我们需要初始化棋盘,因为棋盘上一开始就需要有两个随机的2或4生成。
3.初始化之后我们需要做的是统计棋盘里面的空位,创建一个新的列表用于存储空位的坐标。
4.在大方向上我们就可以进行这个游戏了,需要一个while循环,每次循环开始我们将棋盘格式化输出到控制台,并要求输入一个指令如将棋盘上下左右移动(wsad),相邻相同的数字进行合并
5.我只直接实现了向左移动合并数字,剩下的四个方向我通过旋转棋盘在向左移动合并来实现,我们的棋盘实际上就是一个矩阵如下:
[
['', '', '', ''],
['', '', '', ''],
['', '', '', ''],
['', '', '', '']]
我们以嵌套列表的方式来实现。
向上移动合并就需要:先棋盘左转90度,再向左移动合并,再向右转90度
向下移动合并就需要:先棋盘右转90度,再向左移动合并,再向左转90度
向上移动合并就需要:先棋盘左转90度,再向上移动合并,再向右转90度
这样我们就可以实现2048的核心,数字的移动和合并。
6.在我们每对棋盘进行了一个方向的移动合并以后我们还需要在没有数字的位置去添加一个棋子
7.然后我们还需要一个判断游戏输掉的函数,每行每列没有相邻相同的元素,且没有空位此时游戏结束,可以先判断每行的,再将棋盘旋转来判断每列的。
8.还有一个文件用于存储最高分
9.梳理整个游戏的执行流程,游戏开始初始化棋盘,进入循环输出棋盘最高分和当前分数,选择合并的方向或者重新开始游戏或者退出游戏,统计棋盘的空位,判断是否输掉了游戏,没输就随机添加一个2或4,进入下一个循环。
接下来会一一介绍实现各个功能的函数:
1.为我们的类定义几个属性:
def __init__(self):
self.board_list = [
['', '', '', ''],
['', '', '', ''],
['', '', '', ''],
['', '', '', '']]
# 得分
self.score = 0
f = open("最高分.txt", "r")
a = f.read()
self.best = int(a)
# 空位
self.board_empty = []
board_list是我们的矩阵,用来保存棋子的,score是得分,best是最高得分,board_empty是用来存储空位坐标的列表。
2.格式化输出棋盘:
def print_bora(self):
"""
打印棋盘
:return:
"""
print('''SCORE:{} BEST:{}
+-----+-----+-----+-----+
|{:^5}|{:^5}|{:^5}|{:^5}|
+-----+-----+-----+-----+
|{:^5}|{:^5}|{:^5}|{:^5}|
+-----+-----+-----+-----+
|{:^5}|{:^5}|{:^5}|{:^5}|
+-----+-----+-----+-----+
|{:^5}|{:^5}|{:^5}|{:^5}|
+-----+-----+-----+-----+
w(up),s(down),a(left),d(right)
r(restart),q(exit)'''.format(self.score, self.best,
*self.board_list[0],
*self.board_list[1],
*self.board_list[2],
*self.board_list[3]))
使用的format,可以方便的输出我们的矩阵。
3.初始化棋盘的方法:
def restart(self):
# 初始化棋盘
self.board_list = [
['', '', '', ''],
['', '', '', ''],
['', '', '', ''],
['', '', '', '']]
# 随机两个位置
# 随机2,4
while True:
t1 = (randrange(len(self.board_list)), randrange(len(self.board_list[0])))
t2 = (randrange(len(self.board_list)), randrange(len(self.board_list[0])))
if t1 != t2:
break
self.board_list[t1[0]][t1[1]] = randrange(2, 5, 2)
self.board_list[t2[0]][t2[1]] = randrange(2, 5, 2)
是board_list重新变为空矩阵,随机抽取两个位置,这两个位置不能是同一个位置,为这两个位置随机的添加2或者4其中之一
4.随机在棋盘上某个位置添加随机的2,4,生成之前需要一个方法来统计棋盘中的空位:
def Win_game(self):
"""
赢得游戏
:return:
"""
# 判断是否赢了,并且判断是否有空位
self.board_empty = []
for i in range(len(self.board_list)):
for j in range(len(self.board_list[i])):
# if self.board_list[i][j] == 2048:
# return True
if self.board_list[i][j] == '':
self.board_empty.append((i, j))
return False
def add_pice(self):
"""
添加棋子,在空白的位置添加2,4
:return:
"""
# 先随机位置,再随机值
if self.board_empty:
# 提取并删除这个随机位置
p = self.board_empty.pop(randrange(len(self.board_empty)))
# 再随机生成2,4
self.board_list[p[0]][p[1]] = randrange(2, 5, 2)
每次统计我们都先将board_empty置空,然后遍历矩阵的每一个位置,判断是否为空是就将该位置的坐标加入board_empty。
添加棋子,我们需要在board_empty中随机选取一个坐标,提取并删除这个坐标,再在该位置上生成2或4。
5.介绍核心方法,数字的向左移动并合并相邻的相等的数字:
def row_left_oper(self, row):
"""
向左把一行列表进行2048操作
:param row:
:return:
"""
temp = []
for item in row:
if item:
temp.append(item)
new_row = []
flag = True # 用于跳过相邻两个相等后的判断
for i in range(len(temp)):
if flag:
if i + 1 < len(temp) and temp[i] == temp[i + 1]:
new_row.append(temp[i] * 2)
self.score += temp[i] * 2
if self.score > self.best:
self.best = self.score
f = open("最高分.txt", "w")
f.write(str(self.best))
f.close()
flag = False
else:
new_row.append(temp[i])
else:
flag = True
n = len(row)
for i in range(n - len(new_row)):
new_row.append('')
return new_row
此方法需要传入一个参数一个列表,定义一个临时列表temp将row中的所有元素添加到temp中,此时temp中不存在有空的元素了,遍历我们的row,flag做一个标记,如果flag=True则进入if满足条件的就进行合并,并将得分赋值给score,并判断score是否大于了best是就将score赋值给best,进行了合并就将flag赋值为False,相邻元素不等的就直接添加进新列表。如果flag=False表示已经进行了一次相邻合并下一个进来的元素就不需要进行合并操作,进入else将flag赋值为True即可,如果new_row的长度小于row的长度就为new_row添加空元素,返回值为new_row。
此时向左进行移动合并就已经完成了,接下来介绍如何通过旋转矩阵实现其他方向上的合并。
6.向左旋转矩阵90度,向右旋转矩阵90度
def turn_right(self):
"""
将棋盘向右旋转90度
:return:
"""
self.board_list = [list(x[::-1]) for x in zip(*self.board_list)]
def turn_left(self):
"""
将棋盘向左旋转90度
:return:
"""
for i in range(3):
self.board_list = [list(x[::-1]) for x in zip(*self.board_list)]
旋转矩阵采用的是zip函数,可以将矩阵转置,再将每一行倒序就可以完成矩阵向右旋转90度,向左旋转90度只需要向右旋转3次即可。
7.上下左右的2048操作:
def move_up(self):
# 先左转
self.turn_left()
# 再向左操作
self.move_left()
# 再右转
self.turn_right()
def move_down(self):
# 先右转90度
self.turn_right()
# 再向左操作
self.move_left()
# 再转回来
self.turn_left()
def move_left(self):
for i in range(len(self.board_list)):
self.board_list[i] = self.row_left_oper(self.board_list[i])
def move_right(self):
# 先左转
self.turn_left()
# 再向上操作、
self.move_up()
# 再右转
self.turn_right()
向左合并实现后其他方向就很简单了 。
向上移动合并就需要:先棋盘左转90度,再向左移动合并,再向右转90度,
向下移动合并就需要:先棋盘右转90度,再向左移动合并,再向左转90度,
向上移动合并就需要:先棋盘左转90度,再向上移动合并,再向右转90度。
8.判断游戏是否结束,即棋盘上不再有空位相邻位置不再有相等的数字:
def Lose_game(self):
"""
输掉游戏
:return:
"""
if not self.board_empty:
# 判断每行每列有没有相等的元素
# 判断每一行
for i in range(len(self.board_list)):
for j in range(len(self.board_list[i]) - 1):
if self.board_list[i][j] == self.board_list[i][j + 1]:
return False
# 判断每一列
# 先右转棋盘
self.turn_right()
for i in range(len(self.board_list)):
for j in range(len(self.board_list[i]) - 1):
if self.board_list[i][j] == self.board_list[i][j + 1]:
self.turn_left()
return False
return True
return False
如果board_empty为空则先遍历 board_list的每一行判断其是否有相邻相等的元素,再旋转棋盘使列变成行判断每列是否有相邻想等的元素,判断完之后将棋盘旋转回去。
def start(self):
"""
游戏开始方法
:return:
"""
self.restart()
while True:
self.print_bora()
code = input("请输入指令>>>:")
if code == 'w':
# 向上
self.move_up()
elif code == 's':
# 向下
self.move_down()
elif code == 'a':
# 向左
self.move_left()
elif code == 'd':
# 向右
self.move_right()
elif code == 'r':
# 重启
self.restart()
self.score = 0
continue
elif code == 'q':
# 退出
exit('退出')
else:
print('你的输入有误,请重新输入')
continue
# 判断游戏是否win赢了
self.Win_game()
# print('You Win!')
# break
# 判断游戏是否输了
if self.Lose_game():
print('You Lose!')
break
self.add_pice()
进入方法初始化棋盘,进入循环输出棋盘最高分和当前分数,选择合并的方向或者重新开始游戏或者退出游戏,统计棋盘的空位,判断是否输掉了游戏,没输就随机添加一个2或4,进入下一个循环。
至此在控制台操作的2048就完成了,展示一下运行结果:
下面是图形化界面的2048小游戏,目前还有一点小问题没有解决先放这里:
import turtle
from random import randrange
# boundary = turtle.Screen()
# boundary.setup(430, 630, 500, 10)
# boundary.bgcolor("gray")
# boundary.title('2048')
def Back_gery():
boundary = turtle.Screen()
boundary.setup(430, 630, 500, 10)
boundary.bgcolor('gray')
boundary.title('2048')
boundary.register_shape('2048/bg.gif')
boundary.register_shape('2048/title.gif')
boundary.register_shape('2048/score.gif')
boundary.register_shape('2048/top_score.gif')
boundary.register_shape('2048/2.gif')
boundary.register_shape('2048/4.gif')
boundary.register_shape('2048/8.gif')
boundary.register_shape('2048/16.gif')
boundary.register_shape('2048/32.gif')
boundary.register_shape('2048/64.gif')
boundary.register_shape('2048/128.gif')
boundary.register_shape('2048/256.gif')
boundary.register_shape('2048/1024.gif')
boundary.register_shape('2048/2048.gif')
boundary.register_shape('2048/4096.gif')
boundary.register_shape('2048/8192.gif')
boundary.tracer(0)
class Background(turtle.Turtle):
def __init__(self):
super().__init__()
self.penup()
self.ht()
self.allpos = [[(-150, 50), (-50, 50), (50, 50), (150, 50)],
[(-150, -50), (-50, -50), (50, -50), (150, -50)],
[(-150, -150), (-50, -150), (50, -150), (150, -150)],
[(-150, -250), (-50, -250), (50, -250), (150, -250)]]
self.board_list = [
['', '', '', ''],
['', '', '', ''],
['', '', '', ''],
['', '', '', '']]
self.board_empty = []
self.score = 0
f = open("最高分.txt", "r")
a = f.read()
self.best = int(a)
self.score = 0
def show_text(self):
self.color('white', 'white')
self.goto(-215, 120)
self.begin_fill()
self.pd()
self.goto(215, 120)
self.goto(215, 110)
self.goto(-215, 110)
self.end_fill()
self.pu()
self.shape("2048/title.gif")
self.goto(-125, 210)
self.stamp()
self.shape("2048/score.gif")
self.goto(125, 245)
self.stamp()
self.shape('2048/top_score.gif')
self.goto(125, 170)
self.stamp()
def show_back(self):
"""
主入口
:return:
"""
Back_gery()
self.show_text()
self.show_score()
self.show_best()
self.restart()
# self.shape("2048/bg.gif")
# for i in range(len(self.allpos)):
# for j in range(len(self.allpos[0])):
# self.goto(self.allpos[i][j])
# self.stamp()
self.Win_game()
boundary = turtle.Screen()
boundary.listen()
boundary.onkey(self.restart, '')
boundary.onkey(self.move_left, 'Left')
boundary.onkey(self.move_up, 'Up')
boundary.onkey(self.move_down, 'Down')
boundary.onkey(self.move_right, 'Right')
turtle.done()
def show_score(self):
"""
画出得分
:return:
"""
self.goto(125, 210)
# self.clear()
self.write(f'{self.score}', align='center', font=('Arial', 20, 'bold'))
def show_best(self):
"""
画出历史最高分
:return:
"""
self.goto(125, 140)
# self.clear()
self.write(f'{self.best}', align='center', font=('Arial', 20, 'bold'))
def restart(self):
"""
初始化棋盘,并生成随机的2,4
:return:
"""
self.clear()
self.show_text()
self.score = 0
self.show_score()
self.board_list = [
['', '', '', ''],
['', '', '', ''],
['', '', '', ''],
['', '', '', '']]
self.shape("2048/bg.gif")
for i in range(len(self.allpos)):
for j in range(len(self.allpos[0])):
self.goto(self.allpos[i][j])
self.stamp()
while True:
t1 = (randrange(len(self.allpos)), randrange(len(self.allpos[0])))
t2 = (randrange(len(self.allpos)), randrange(len(self.allpos[0])))
if t1 != t2:
break
list1 = [t1, t2]
for i in list1:
t3 = randrange(2, 5, 2)
self.shape(f'2048/{t3}.gif')
self.goto(self.allpos[i[0]][i[1]])
self.board_list[i[0]][i[1]] = t3
self.stamp()
def add_pice(self):
"""
添加棋子,在空白的位置添加2,4
:return:
"""
# 先随机位置,再随机值
if self.board_empty:
# 提取并删除这个随机位置
p = self.board_empty.pop(randrange(len(self.board_empty)))
# 再随机生成2,4
a = randrange(2, 5, 2)
self.shape(f'2048/{a}.gif')
self.goto(self.allpos[p[0]][p[1]])
self.board_list[p[0]][p[1]] = a
self.stamp()
def Win_game(self):
"""
赢得游戏
:return:
"""
# 判断是否赢了,并且判断是否有空位
self.board_empty = []
for i in range(len(self.board_list)):
for j in range(len(self.board_list[i])):
# if self.board_list[i][j] == 2048:
# return True
if self.board_list[i][j] == '':
self.board_empty.append((i, j))
return False
def Lose_game(self):
"""
输掉游戏
:return:
"""
if not self.board_empty:
# 判断每行每列有没有相等的元素
# 判断每一行
for i in range(len(self.board_list)):
for j in range(len(self.board_list[i]) - 1):
if self.board_list[i][j] == self.board_list[i][j + 1]:
return False
# 判断每一列
# 先右转棋盘
self.turn_right()
for i in range(len(self.board_list)):
for j in range(len(self.board_list[i]) - 1):
if self.board_list[i][j] == self.board_list[i][j + 1]:
self.turn_left()
return False
return True
return False
def write_qipan(self):
self.clear()
self.show_text()
self.show_score()
self.show_best()
if self.score>self.best:
self.show_best()
for i in range(len(self.board_list)):
for j in range(len(self.board_list[i])):
if self.board_list[i][j] == '':
self.shape('2048/bg.gif')
b = self.allpos[i][j]
self.goto(b)
self.stamp()
else:
a = self.board_list[i][j]
b = self.allpos[i][j]
self.shape(f'2048/{a}.gif')
self.goto(b)
self.stamp()
# self.Win_game()
if self.Lose_game():
self.penup()
self.color('blue')
self.penup()
self.left(180)
self.forward(150)
self.write(f'游戏结束,您的得分是{self.score}', align='center', font=('黑体', 20, 'bold'))
# for i in self.board_list:
# print(i)
# print(self.score)
# print("--------------------------")
def move_up(self):
# 先左转
self.turn_left()
# 再向左操作
self.left_qipan()
# 再右转
self.turn_right()
self.Win_game()
self.add_pice()
self.write_qipan()
def move_down(self):
# 先右转90度
self.turn_right()
# 再向左操作
self.left_qipan()
# 再转回来
self.turn_left()
self.Win_game()
self.add_pice()
self.write_qipan()
def left_qipan(self):
for i in range(len(self.board_list)):
self.board_list[i] = self.row_left_oper(self.board_list[i])
def move_left(self):
self.left_qipan()
self.Win_game()
self.add_pice()
self.write_qipan()
def move_right(self):
# 先右转
self.turn_right()
self.turn_right()
# 再向左操作、
self.left_qipan()
# 再左转
self.turn_left()
self.turn_left()
self.Win_game()
self.add_pice()
self.write_qipan()
def row_left_oper(self, row):
"""
向左把一行列表进行2048操作
:param row:
:return:
"""
temp = []
for item in row:
if item:
temp.append(item)
new_row = []
flag = True # 用于跳过相邻两个相等后的判断
for i in range(len(temp)):
if flag:
if i + 1 < len(temp) and temp[i] == temp[i + 1]:
new_row.append(temp[i] * 2)
self.score += temp[i] * 2
if self.score > self.best:
self.best = self.score
f = open("最高分.txt", "w")
f.write(str(self.best))
f.close()
flag = False
else:
new_row.append(temp[i])
else:
flag = True
n = len(row)
for i in range(n - len(new_row)):
new_row.append('')
return new_row
def turn_right(self):
"""
将棋盘向右旋转90度
:return:
"""
self.board_list = [list(x[::-1]) for x in zip(*self.board_list)]
def turn_left(self):
"""
将棋盘向左旋转90度
:return:
"""
for i in range(3):
self.board_list = [list(x[::-1]) for x in zip(*self.board_list)]
if __name__ == '__main__':
background = Background()
background.show_back()
运行结果: