【python+情人节】玩个俄罗斯方块都是爱你的形状——三、自动拼图

本项目效果如图
动画过程已投稿b站:https://www.bilibili.com/video/av88671119
在这里插入图片描述

目录

一、思路分析

二、手动拼图

三、自动拼图

四、动画展示

======================= 大爽歌作,made by big shuang =======================

三、自动拼图

1、介绍分析

这里再先介绍一遍
本部分要通过算法实现俄罗斯方块拼出指定的形状,主要是解决一个纯算法问题,结果通过控制台输出(由于没有必要,所以本部分没有做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)],
}

不能有重叠,不能有剩余
用图来表示,即用如下七种俄罗斯方块拼出下方的粉红色爱心形状

在这里插入图片描述
在这里插入图片描述
这里我使用回溯法去寻找

2、回溯法详细过程分析

按列数从左到右,
——遍历所有种类的俄罗斯方块的所有形状,
————如果降落后和心形贴合
——————检查是否全部都拼完了
————————如果是,则返回True
————————否则进一步递归的去调用方法本身并返回
————如果不贴合,继续遍历
——如果遍历完了不贴合,则返回False

3、回溯法代码

依据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

4、回溯法代码优化

上面第三部分的代码要运行很久(我本地渣电脑运行三个小时都没看到结果。。。我都有点怀疑是不是代码逻辑有问题了)
这里很有必要对代码原有的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},
]
  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值