前言
前几天刷抖音,发现有人在玩日历拼图,看起来还挺好玩的。一个拼图号称能拼出全年365天,于是上某宝一看,还有难度更高一级的,带星期的玩法,于是入手了一个玩。别说,还挺有意思的,就是有点浪费时间,有时候得花半个小时的时间去拼当天的拼图。
边玩边想,这个东西其实没什么技术含量,就是暴力法一直尝试,完全可以交给代码来完成。于是上 github 一搜,果然有大神写了代码,但大神用的是 C++,并且是不带星期的简单玩法。着手改造:
- 代码全部改成 go
- 加上星期
- dfs 剪枝优化
- 启动 http server
- 添加前端页面
完整代码地址:
Github 地址:https://github.com/LeoNumber1/calender_puzzle
Gitee 地址:https://gitee.com/leono1/calender_puzzle
线上体验地址:点击体验
一、日历拼图是什么?
【日历拼图】是一款益智游戏,有带星期和不带星期两种。
1. 不带星期
底板为7乘7的格子,去掉右上角的两块和右下角的四块。拼图块总共有8个块,能拼出一年365天的日期。如下所示:
2. 带星期
底板为7乘8的格子,去掉右上角的两块和左下角的四块。拼图块总共有10个块,能拼出365*7的日期。堪称“顶级难度”的日历拼图,据说有2604种拼法,如下所示:
二、主要思想
- 初始化底板map;
- 底板中加入墙体和日期;
- 初始化拼图块puzzle;
- 每个拼图块顺时针旋转90°四次,再镜像后顺时针旋转90°四次,将每次的结果去重后保存,得到每个拼图块能拼的所有形状shape;
- 遍历每一个拼图块及其形状,在底板上找一个合适的位置放下;检查是否能将其放置在map上的xy位置处,左上角对齐xy,如果能放置,则放置,设置map对应区域;
- dfs 剪枝优化,如果有连通区域小于最小拼图块,则回溯;
- 重复步骤5,如果发现还有块不能放下,则回溯。直到所有的拼图块都能完美放入底板中。
三、主要代码
1. 定义拼图块
拼图块结构体代码如下:
type Puzzle struct {
ShapeNum *int // 当前拼图块有多少变种
X, Y *int // 当前在图形中,左上角右上角坐标
ShapeIndex int // 当前拼图的形状索引
allShapes [constant.PUZZLE_NUM]Shape // 所有不同的形状
}
// Shape struct for A block shape
type Shape struct {
Height int // 当前形状高
Width int // 当前形状宽
MyShape [][]int // 当前形状
}
拼图块变换:
// Rotate 顺时针旋转90度
func (sh Shape) Rotate() Shape {
arr := make([][]int, sh.Width)
for i := range arr {
arr[i] = make([]int, sh.Height)
for j := range arr[i] {
arr[i][j] = sh.MyShape[sh.Height-1-j][i]
}
}
return Shape{
Height: sh.Width,
Width: sh.Height,
MyShape: arr,
}
}
// Flip 左右镜像翻转
func (sh Shape) Flip() Shape {
arr := make([][]int, sh.Height)
for i := range arr {
arr[i] = make([]int, sh.Width)
for j := range arr[i]