本项目效果如图
动画过程已投稿b站:https://www.bilibili.com/video/av88671119
======================= 大爽歌作,made by big shuang =======================
这里再先介绍一遍
本部分要通过算法实现俄罗斯方块拼出指定的形状,主要是解决一个纯算法问题,结果通过控制台输出(由于没有必要,所以本部分没有做gui)。
具体的
一直一个爱心形状文本如下(文本名为)
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 1 1 1 0 0 0 0 0 1 1 1 0 0 0 0
0 0 0 0 1 1 1 1 1 1 0 1 1 1 1 1 1 0 0 0
0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0
0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0
0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0
0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0
0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0
0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0
0 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0 0 0
0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0
其中0为背景,1位爱心的方格
要用如下七种俄罗斯方块拼出上方所有的1所代表的的爱心,
{
"O": [(-1, -1), (0, -1), (-1, 0), (0, 0)],
"S": [(-1, 0), (0, 0), (0, -1), (1, -1)],
"T": [(-1, 0), (0, 0), (0, -1), (1, 0)],
"I": [(0, 1), (0, 0), (0, -1), (0, -2)],
"L": [(-1, 0), (0, 0), (-1, -1), (-1, -2)],
"J": [(-1, 0), (0, 0), (0, -1), (0, -2)],
"Z": [(-1, -1), (0, -1), (0, 0), (1, 0)],
}
不能有重叠,不能有剩余
用图来表示,即用如下七种俄罗斯方块拼出下方的粉红色爱心形状
按列数从左到右,
——遍历所有种类的俄罗斯方块的所有形状,
————如果降落后和心形贴合
——————检查是否全部都拼完了
————————如果是,则返回True
————————否则进一步递归的去调用方法本身并返回
————如果不贴合,继续遍历
——如果遍历完了不贴合,则返回False
依据2中的思路写出的代码如下
当然这个时候有个问题,由于回溯法太过耗时,所以我这边会一直运行却不出结果,这个在本文第四部分优化下,本部分就只通过代码展示回溯法的思路
puzzle_auto.py: 主代码。其中solve是递归方法
# usr/bin/env python
# -*- coding:utf-8- -*-
# 使用回溯法用七种俄罗斯方块拼出爱心形状
from util import *
from constants import *
class Solution:
def __init__(self, txt_path):
self.love_list = read_love_from_txt(txt_path)
self.love_count = 0
for row in self.love_list:
for i in row:
if i == "1":
self.love_count += 1
self.r = len(self.love_list)
self.c = len(self.love_list[0])
self.board = None # 记录每个位置已有的俄罗斯方格
self.block_list = []
self.block_id = 0
self.refresh_board()
def refresh_board(self):
self.board = [
['' for ci in range(self.c)] for ri in range(self.r)
]
for block in self.block_list:
shape_type = block['kind']
cc, cr = block['cr']
cell_list = block['cell_list']
for cell in cell_list:
cell_c, cell_r = cell
c = cell_c + cc
r = cell_r + cr
self.board[r][c] = shape_type
def solve(self):
"""
列数从左到右, 遍历所有种类的俄罗斯方块的所有形状,
如果降落后和心形贴合,
检查是否全部都拼完了,如果是,则返回True
否则进一步递归的去调用方法本身并返回
如果不贴合,继续遍历
如果遍历完了不贴合,则返回False
:return:
"""
for shape in SHAPE_LIST:
cell_list = SHAPES[shape]
for ci in range(self.c):
for angel in range(4):
rotate_list = get_cell_list_by_angle(cell_list, angel)
ri = self.get_land_r(rotate_list, ci)
if self.check_match((ci, ri), rotate_list):
cur_block = {
"cr":(ci, ri),
"kind": shape,
"cell_list": rotate_list,
"angle": angel
}
self.block_list.append(cur_block)
# 检查是否全部都拼完了
if self.check_match_all():
return True
else:
self.refresh_board()
res = self.solve()
if res:
return True
else:
self.block_list.pop()
self.refresh_board()
return False
def get_land_r(self, cell_list, ci):
# 获取当前俄罗斯方块降落后的行位置
ri = 0
for ri in range(2, self.r):
# 从上到下依次尝试能否移动该行,如果这行移动不到,则说明上一行是降落的着陆点
if not self.check_move((ci, ri), cell_list, (0, 0)):
return ri - 1
return self.r - 1
def check_move(self, cr, cell_list, direction):
# 判断某个俄罗斯方块的方格能否在某个位置摆放
cc, cr = cr
cell_list = cell_list
for cell in cell_list:
cell_c, cell_r = cell
c = cell_c + cc + direction[0]
r = cell_r + cr + direction[1]
# 判断该位置是否超出左右边界,以及下边界
# 一般不判断上边界,因为俄罗斯方块生成的时候,可能有一部分在上边界之上还没有出来
if c < 0 or c >= self.c or r >= self.r:
return False
# 必须要判断r不小于0才行,具体原因你可以不加这个判断,试试会出现什么效果
if r >= 0 and self.board[r][c] not in ['', 'R']:
return False
return True
def check_match(self, cr, cell_list):
# cell_list 检查是否匹配 love_list, 全部匹配1
c, r = cr
for cell in cell_list:
cc, cr = cell
rc, rr = cc+c, cr+r
if rr >= self.r or rc < 0 or rc >= self.c:
return False
if self.love_list[rr][rc] != "1":
return False
return True
def check_match_all(self):
if 4 * len(self.block_list) == self.love_count:
return True
else:
return False
def print_block_list(self):
if self.block_list:
print('[')
for block in self.block_list:
print(' ', end='')
print(block, end='')
print(',')
print(']')
if __name__ == '__main__':
txt_path = "love3.txt"
solution = Solution(txt_path)
solution.solve()
solution.print_block_list()
其他支持代码
util.py
# usr/bin/env python
# -*- coding:utf-8- -*-
def read_love_from_txt(txt_path):
# 从txt中读取二维爱心列表,并返回
with open(txt_path, 'r') as f:
fl = f.readlines()
love_list = []
for line in fl:
line = line.strip('\n')
new_line = line.split(' ')
love_list.append(new_line)
return love_list
def get_cell_list_by_angle(cell_list, angle):
# 将当前的 cell_list旋转指定的角度
angle_dict = {
0: (1, 0, 0, 1),
1: (0, 1, -1, 0),
2: (-1, 0, 0, -1),
3: (0, -1, 1, 0),
}
a, b, c, d = angle_dict[angle]
if angle == 0:
return cell_list
rotate_cell_list = []
for cell in cell_list:
cc, cr = cell
rc, rr = a * cc + b * cr, c*cc+d*cr
rotate_cell_list.append((rc, rr))
return rotate_cell_list
# usr/bin/env python
# -*- coding:utf-8- -*-
C = 20
R = 20
CELL_SIZE = 30
FPS = 100
# 定义各种形状
SHAPES = {
"O": [(-1, -1), (0, -1), (-1, 0), (0, 0)],
"S": [(-1, 0), (0, 0), (0, -1), (1, -1)],
"T": [(-1, 0), (0, 0), (0, -1), (1, 0)],
"I": [(0, 1), (0, 0), (0, -1), (0, -2)],
"L": [(-1, 0), (0, 0), (-1, -1), (-1, -2)],
"J": [(-1, 0), (0, 0), (0, -1), (0, -2)],
"Z": [(-1, -1), (0, -1), (0, 0), (1, 0)],
}
# 定义各种颜色,用于给第二第三阶段拼图上色
# ALL_COLORS = {
# # 七种俄罗斯方块的颜色
# "O": "#d25b6a",
# "S": "#d2835b",
# "T": "#e5e234",
# "I": "#83d05d",
# "L": "#2862d2",
# "J": "#35b1c0",
# "Z": "#5835c0",
#
# # 其他颜色
# "": "#CCCCCC",
# "R": "#ffcccc",
# "D": "#ff3366",
# "line": "red"
# }
# 绘制动画时将七种俄罗斯方块全部换成深红色
ALL_COLORS = {
# 七种俄罗斯方块的颜色
"O": "#ff3366",
"S": "#ff3366",
"T": "#ff3366",
"I": "#ff3366",
"L": "#ff3366",
"J": "#ff3366",
"Z": "#ff3366",
# 其他颜色
"": "#CCCCCC",
"R": "#ffcccc",
"D": "#ff3366",
"line": "red"
}
SHAPE_LIST = ["O", "S", "T", "I", "L", "J", "Z"]
DIRECTION = {
"W": (0, -1),
"S": (0, 1),
"A": (-1, 0),
"D": (1, 0),
}
附件
love3.txt
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 1 1 1 0 0 0 0 0 1 1 1 0 0 0 0
0 0 0 0 1 1 1 1 1 1 0 1 1 1 1 1 1 0 0 0
0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0
0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0
0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0
0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0
0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0
0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0
0 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0 0 0
0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0
上面第三部分的代码要运行很久(我本地渣电脑运行三个小时都没看到结果。。。我都有点怀疑是不是代码逻辑有问题了)
这里很有必要对代码原有的solve
方法进行一个优化
其实并没有做很复杂的工作
只是添加了这样一个逻辑:
从下往上,从左往右进行匹配
优化后的方法命名为advanced_solve
同时添加了几个辅助方法:
advanced_solve
refresh_board_bottom_cr
修改后的puzzle_auto.py如下
# usr/bin/env python
# -*- coding:utf-8- -*-
# 使用回溯法用七种俄罗斯方块拼出爱心形状
from util import *
from constants import *
class Solution:
def __init__(self, txt_path):
self.love_list = read_love_from_txt(txt_path)
self.love_count = 0
for row in self.love_list:
for i in row:
if i == "1":
self.love_count += 1
self.r = len(self.love_list)
self.c = len(self.love_list[0])
self.board = None # 记录每个位置已有的俄罗斯方格
self.block_list = []
self.block_id = 0
self.refresh_board()
# 相对于第三部分代码新增
self.bc = -1
self.br = -1
self.refresh_board_bottom_cr()
def refresh_board_bottom_cr(self):
for ri in range(self.r-1, -1, -1):
for ci in range(self.c):
if self.love_list[ri][ci] == "1" and self.board[ri][ci] == "":
self.bc = ci
self.br = ri
return
def refresh_board(self):
self.board = [
['' for ci in range(self.c)] for ri in range(self.r)
]
for block in self.block_list:
shape_type = block['kind']
cc, cr = block['cr']
cell_list = block['cell_list']
for cell in cell_list:
cell_c, cell_r = cell
c = cell_c + cc
r = cell_r + cr
self.board[r][c] = shape_type
def solve(self):
"""
列数从左到右, 遍历所有种类的俄罗斯方块的所有形状,
如果降落后和心形贴合,
检查是否全部都拼完了,如果是,则返回True
否则进一步递归的去调用方法本身并返回
如果不贴合,继续遍历
如果遍历完了不贴合,则返回False
:return:
"""
for shape in SHAPE_LIST:
cell_list = SHAPES[shape]
for ci in range(self.c):
for angel in range(4):
rotate_list = get_cell_list_by_angle(cell_list, angel)
ri = self.get_land_r(rotate_list, ci)
if self.check_match((ci, ri), rotate_list):
cur_block = {
"cr": (ci, ri),
"kind": shape,
"cell_list": rotate_list,
"angle": angel
}
self.block_list.append(cur_block)
# 检查是否全部都拼完了
if self.check_match_all():
return True
else:
self.refresh_board()
res = self.solve()
if res:
return True
else:
self.block_list.pop()
self.refresh_board()
return False
def advanced_solve(self):
"""
先从下到上,从左到右进行匹配
:return:
"""
for shape in SHAPE_LIST:
cell_list = SHAPES[shape]
for ci in range(self.bc - 2, self.bc + 3):
if ci < 0 or ci >= self.c:
continue
for angel in range(4):
rotate_list = get_cell_list_by_angle(cell_list, angel)
ri = self.get_land_r(rotate_list, ci)
if self.check_match_bottom((ci, ri), rotate_list):
cur_block = {
"cr": (ci, ri),
"kind": shape,
"cell_list": rotate_list,
"angle": angel
}
self.block_list.append(cur_block)
# 检查是否全部都拼完了
if self.check_match_all():
return True
else:
self.refresh_board()
self.refresh_board_bottom_cr()
res = self.advanced_solve()
if res:
return True
else:
self.block_list.pop()
self.refresh_board()
self.refresh_board_bottom_cr()
return False
def get_land_r(self, cell_list, ci):
# 获取当前俄罗斯方块降落后的行位置
ri = 0
for ri in range(2, self.r):
# 从上到下依次尝试能否移动该行,如果这行移动不到,则说明上一行是降落的着陆点
if not self.check_move((ci, ri), cell_list, (0, 0)):
return ri - 1
return self.r - 1
def check_move(self, cr, cell_list, direction):
# 判断某个俄罗斯方块的方格能否在某个位置摆放
cc, cr = cr
cell_list = cell_list
for cell in cell_list:
cell_c, cell_r = cell
c = cell_c + cc + direction[0]
r = cell_r + cr + direction[1]
# 判断该位置是否超出左右边界,以及下边界
# 一般不判断上边界,因为俄罗斯方块生成的时候,可能有一部分在上边界之上还没有出来
if c < 0 or c >= self.c or r >= self.r:
return False
# 必须要判断r不小于0才行,具体原因你可以不加这个判断,试试会出现什么效果
if r >= 0 and self.board[r][c] not in ['', 'R']:
return False
return True
def check_match(self, cr, cell_list):
# cell_list 检查是否匹配 love_list, 全部匹配1
c, r = cr
for cell in cell_list:
cc, cr = cell
rc, rr = cc+c, cr+r
if rr >= self.r or rc < 0 or rc >= self.c:
return False
if self.love_list[rr][rc] != "1":
return False
return True
def check_match_bottom(self, cr, cell_list):
# 不仅要检查 cell_list 是否匹配 love_list, 全部匹配1
# 也要检查上一个最下面最靠左的未被覆盖的粉红方格是否被当前的俄罗斯方块覆盖
c, r = cr
isBottom = False
for cell in cell_list:
cc, cr = cell
rc, rr = cc+c, cr+r
if rr >= self.r or rc < 0 or rc >= self.c:
return False
if self.love_list[rr][rc] != "1":
return False
if self.bc == rc and self.br == rr:
isBottom = True
return isBottom
def check_match_all(self):
if 4 * len(self.block_list) == self.love_count:
return True
else:
return False
def print_block_list(self):
if self.block_list:
print('[')
for block in self.block_list:
print(' ', end='')
print(block, end='')
print(',')
print(']')
if __name__ == '__main__':
txt_path = "love3.txt"
solution = Solution(txt_path)
solution.advanced_solve()
solution.print_block_list()
使用这个advanced_solve
大概运行十几分钟能运行出结果(确实有点慢)
其输出结果如下(把这个结果保存到result.py然后交给第四节的去动画展示就可以看到动画过程)
[
{'cr': (9, 15), 'kind': 'S', 'cell_list': [(0, -1), (0, 0), (1, 0), (1, 1)], 'angle': 3},
{'cr': (10, 14), 'kind': 'S', 'cell_list': [(0, -1), (0, 0), (1, 0), (1, 1)], 'angle': 3},
{'cr': (8, 13), 'kind': 'T', 'cell_list': [(1, 0), (0, 0), (0, 1), (-1, 0)], 'angle': 2},
{'cr': (11, 13), 'kind': 'S', 'cell_list': [(0, -1), (0, 0), (1, 0), (1, 1)], 'angle': 3},
{'cr': (12, 12), 'kind': 'S', 'cell_list': [(0, -1), (0, 0), (1, 0), (1, 1)], 'angle': 3},
{'cr': (6, 11), 'kind': 'O', 'cell_list': [(1, 1), (0, 1), (1, 0), (0, 0)], 'angle': 2},
{'cr': (8, 11), 'kind': 'O', 'cell_list': [(1, 1), (0, 1), (1, 0), (0, 0)], 'angle': 2},
{'cr': (10, 11), 'kind': 'T', 'cell_list': [(0, -1), (0, 0), (1, 0), (0, 1)], 'angle': 3},
{'cr': (13, 11), 'kind': 'S', 'cell_list': [(0, -1), (0, 0), (1, 0), (1, 1)], 'angle': 3},
{'cr': (5, 10), 'kind': 'T', 'cell_list': [(1, 0), (0, 0), (0, 1), (-1, 0)], 'angle': 2},
{'cr': (14, 10), 'kind': 'S', 'cell_list': [(0, -1), (0, 0), (1, 0), (1, 1)], 'angle': 3},
{'cr': (7, 9), 'kind': 'O', 'cell_list': [(1, 1), (0, 1), (1, 0), (0, 0)], 'angle': 2},
{'cr': (9, 9), 'kind': 'T', 'cell_list': [(0, -1), (0, 0), (1, 0), (0, 1)], 'angle': 3},
{'cr': (11, 9), 'kind': 'O', 'cell_list': [(1, 1), (0, 1), (1, 0), (0, 0)], 'angle': 2},
{'cr': (15, 9), 'kind': 'S', 'cell_list': [(0, -1), (0, 0), (1, 0), (1, 1)], 'angle': 3},
{'cr': (3, 8), 'kind': 'O', 'cell_list': [(1, 1), (0, 1), (1, 0), (0, 0)], 'angle': 2},
{'cr': (5, 8), 'kind': 'O', 'cell_list': [(1, 1), (0, 1), (1, 0), (0, 0)], 'angle': 2},
{'cr': (12, 8), 'kind': 'S', 'cell_list': [(0, -1), (0, 0), (1, 0), (1, 1)], 'angle': 3},
{'cr': (17, 8), 'kind': 'T', 'cell_list': [(0, 1), (0, 0), (-1, 0), (0, -1)], 'angle': 1},
{'cr': (7, 7), 'kind': 'O', 'cell_list': [(1, 1), (0, 1), (1, 0), (0, 0)], 'angle': 2},
{'cr': (9, 7), 'kind': 'S', 'cell_list': [(0, -1), (0, 0), (1, 0), (1, 1)], 'angle': 3},
{'cr': (11, 6), 'kind': 'J', 'cell_list': [(1, 0), (0, 0), (0, 1), (0, 2)], 'angle': 2},
{'cr': (13, 7), 'kind': 'S', 'cell_list': [(0, -1), (0, 0), (1, 0), (1, 1)], 'angle': 3},
{'cr': (4, 7), 'kind': 'T', 'cell_list': [(-1, 0), (0, 0), (0, -1), (1, 0)], 'angle': 0},
{'cr': (5, 6), 'kind': 'S', 'cell_list': [(0, -1), (0, 0), (1, 0), (1, 1)], 'angle': 3},
{'cr': (15, 6), 'kind': 'O', 'cell_list': [(1, 1), (0, 1), (1, 0), (0, 0)], 'angle': 2},
{'cr': (7, 6), 'kind': 'Z', 'cell_list': [(-1, -1), (0, -1), (0, 0), (1, 0)], 'angle': 0},
{'cr': (14, 5), 'kind': 'T', 'cell_list': [(1, 0), (0, 0), (0, 1), (-1, 0)], 'angle': 2},
]