四邻
2025-4-13
(0)
程序简介
四邻游戏,是在国产操作系统某次作品征集中的优秀作品中看到的。玩法是数字拼图,每块拼图分割成上下左右四个部分,要求相邻两块拼图对应的上下或左右部分的数字颜色一致,边缘不考虑,最后完整拼接。在拼图较小时(四乘四),突破口在于有些拼图会出现无相对应位置拼图的情况,例如某拼图顶部是数字 1,而没有其他拼图的底部是数字 1,由此确定这块拼图位于顶部边缘。而拼图较大时,大部分时候都不会出现突破口,还会出现一些相似拼图,例如三个部分都一致的拼图,在实际游戏中会出现没法顺利解的情况,有时候解六乘六半个小时后才发现第二块或者第三块猜错了,会有点想放弃重开,原程序是没有考虑这个问题的。
本程序的按钮,1.新开:当前边长重新生成拼图。2.复原:还原当前拼图。3.提示:针对拼图较大时(五乘五、六乘六)完全没有头绪时,每点一次提示会帮助多还原一块拼图,点两到三次基本就够用了。4.边长:调难度,边长 2~6。5.颜色:调难度,颜色 2~10,建议不动,如果感觉六乘六 10 个数字比较简单,可以调小。
玩法拓展讨论,我原本还想添加旋转功能,更大程度增加难度,但玩过六乘六后放弃了,不加提示成功率就不高。我还想到可以加一些万能拼图来降低难度;加一些墙拼图来改变大拼图形状......
程序运行展示
完整源代码
////////////////////////////////////////
// 程序:四邻
// 作者:Gary
// 编译环境:Visual C++ 2010,EasyX_20211109
// 编写日期:2025.4.13
# include <math.h>
# include <graphics.h>
# include <time.h>
# include <string>
static HWND hOut; // 画布
// 格子
struct Node1
{
int num; // 初始位置
POINT pos[5]; // 点位置
int num_top, num_bottom, num_left, num_right; // 四格位置的值
int num_type; // 状态
};
Node1 box_start[12][6];
// 按钮
struct Node2
{
int posx1, posy1, posx2, posy2;
LPTSTR text;
};
Node2 box_button[6];
// 定义一个类
class Gary
{
public:
void carry(); // 主进程
void initialization(); // 初始化
void move(); // 窗口主视角函数
void time_new(); // 时间更新函数
void exchange(int i, int j, int x, int y); // 交换函数
void check(int i, int j, int x, int y); // 检测函数
void draw(); // 绘制函数
void end(); // 判定函数
void solve(); // 复原函数
void tip(); // 提示函数
int exit_carry; // 主循函数控制参数
int exit_move; // 开始界面控制参数
int length_side; // 难度
int num_click; // 点击
int num_tip; // 提示
int num_color; // 颜色种类
ExMessage m; // 鼠标
COLORREF color_num[16]; // 颜色
TCHAR s[10]; // 字符串
clock_t start_t1; // 游戏开始系统时间参数
clock_t start_t2; // 游戏进行系统时间参数
};
// 复原函数
void Gary::solve()
{
int i, j, x, y;
// 根据初始位置交换
for(i = 0; i < length_side * 2; i++)
{
for(j = 0; j < length_side; j++)
{
if(box_start[i][j].num != i * 10 + j)
{
// 搜寻
for(x = 0; x < length_side * 2; x++)
{
for(y = 0; y < length_side; y++)
{
if(box_start[x][y].num == i * 10 + j)
{
// 交换
exchange(i, j, x, y);
}
}
}
}
}
}
// 绘制
draw();
}
// 判定函数
void Gary::end()
{
int i, j, k;
k = 0;
// 左侧是否填满
for(i = length_side; i < length_side * 2; i++)
{
for(j = 0; j < length_side; j++)
{
if(box_start[i][j].num_type == 1)
{
k++;
}
}
}
// 填满则结束
if(k == length_side * length_side)
{
exit_move = 1;
draw();
MessageBox(hOut, _T("成功了"), _T("来自小豆子的提醒"), MB_OK);
}
}
// 时间更新
void Gary::time_new()
{
// 文字绘制
settextstyle(30, 0, _T("Consolas"));
settextcolor(BLACK);
// 时间更新
start_t2 = clock();
if(( start_t2 - start_t1 ) % 500 == 0)
{
setbkcolor(WHITE);
_stprintf_s(s, _T("%0.1d"), ( start_t2 - start_t1 ) / 1000);
outtextxy(380, 415, s);
FlushBatchDraw();
}
}
// 交换函数
void Gary::exchange(int i, int j, int x, int y)
{
int k;
// 四邻的值
// 上
k = box_start[i][j].num_top;
box_start[i][j].num_top = box_start[x][y].num_top;
box_start[x][y].num_top = k;
// 下
k = box_start[i][j].num_bottom;
box_start[i][j].num_bottom = box_start[x][y].num_bottom;
box_start[x][y].num_bottom = k;
// 左
k = box_start[i][j].num_left;
box_start[i][j].num_left = box_start[x][y].num_left;
box_start[x][y].num_left = k;
// 右
k = box_start[i][j].num_right;
box_start[i][j].num_right = box_start[x][y].num_right;
box_start[x][y].num_right = k;
// 状态
k = box_start[i][j].num_type;
box_start[i][j].num_type = box_start[x][y].num_type;
box_start[x][y].num_type = k;
// 初始位置
k = box_start[i][j].num;
box_start[i][j].num = box_start[x][y].num;
box_start[x][y].num = k;
}
// 检测函数
void Gary::check(int i, int j, int x, int y)
{
if(
// 新二格检查
// 新二格是否在左侧
( x >= length_side && (
// 顶部检查
( y > 0 && box_start[x][y - 1].num_type != 0 && box_start[x][y].num_top != box_start[x][y - 1].num_bottom ) ||
// 底部
( y < length_side - 1 && box_start[x][y + 1].num_type != 0 && box_start[x][y].num_bottom != box_start[x][y + 1].num_top ) ||
// 左侧
( x > length_side && box_start[x - 1][y].num_type != 0 && box_start[x][y].num_left != box_start[x - 1][y].num_right ) ||
// 右侧
( x < length_side * 2 - 1 && box_start[x + 1][y].num_type != 0 && box_start[x][y].num_right != box_start[x + 1][y].num_left ) ) )
||
// 新一格检查
// 新一格是否在左侧,且不为空,即原二格不为空
( i >= length_side && box_start[i][j].num_type == 1 && (
// 顶部检查
( j > 0 && box_start[i][j - 1].num_type != 0 && box_start[i][j].num_top != box_start[i][j - 1].num_bottom ) ||
// 底部
( j < length_side - 1 && box_start[i][j + 1].num_type != 0 && box_start[i][j].num_bottom != box_start[i][j + 1].num_top ) ||
// 左侧
( i > length_side && box_start[i - 1][j].num_type != 0 && box_start[i][j].num_left != box_start[i - 1][j].num_right ) ||
// 右侧
( i < length_side * 2 - 1 && box_start[i + 1][j].num_type != 0 && box_start[i][j].num_right != box_start[i + 1][j].num_left ) ) ))
{
// 换回来
exchange(i, j, x, y);
}
}
// 绘制函数
void Gary::draw()
{
int i, j;
POINT p[3];
// 字体
settextstyle(21 + 5 * int(pow(2.0, 5 - length_side)), 0, _T("Consolas"));
settextcolor(BLACK);
setlinecolor(BLACK);
// 绘制
for(j = 0; j < length_side; j++)
{
for(i = 0; i < length_side * 2; i++)
{
// 不是空格
if(box_start[i][j].num_type == 1)
{
// 顶部
setfillcolor(color_num[box_start[i][j].num_top]);
p[0] = box_start[i][j].pos[0]; p[1] = box_start[i][j].pos[1]; p[2] = box_start[i][j].pos[4];
fillpolygon(p, 3);
setbkcolor(color_num[box_start[i][j].num_top]);
_stprintf_s(s, _T("%0d"), box_start[i][j].num_top);
outtextxy(box_start[i][j].pos[0].x + 180 / length_side - ( length_side >= 4 ? 5 : 10 ),
box_start[i][j].pos[0].y + 180 / length_side - ( 7 * ( 10 - length_side ) + ( length_side == 2 ? 24 : 0 ) ), s);
// 底部
setfillcolor(color_num[box_start[i][j].num_bottom]);
p[0] = box_start[i][j].pos[2]; p[1] = box_start[i][j].pos[3]; p[2] = box_start[i][j].pos[4];
fillpolygon(p, 3);
setbkcolor(color_num[box_start[i][j].num_bottom]);
_stprintf_s(s, _T("%0d"), box_start[i][j].num_bottom);
outtextxy(box_start[i][j].pos[0].x + 180 / length_side - ( length_side >= 4 ? 5 : 10 ),
box_start[i][j].pos[0].y + 180 / length_side + ( 7 + ( 6 - length_side ) / 3 * 2 + ( length_side <= 3 ? 10 : 0 ) + ( length_side == 4 ? 5 : 0 ) ), s);
// 左侧
setfillcolor(color_num[box_start[i][j].num_left]);
p[0] = box_start[i][j].pos[0]; p[1] = box_start[i][j].pos[3]; p[2] = box_start[i][j].pos[4];
fillpolygon(p, 3);
setbkcolor(color_num[box_start[i][j].num_left]);
_stprintf_s(s, _T("%0d"), box_start[i][j].num_left);
outtextxy(box_start[i][j].pos[0].x + 180 / length_side - ( 5 * ( 11 - length_side ) + ( length_side == 3 ? 10 : 0 ) + ( length_side == 2 ? 25 : 0 ) ),
box_start[i][j].pos[0].y + 180 / length_side - ( ( length_side >= 5 ? ( 8 - length_side ) * 5 : ( 2 + int(pow(2.0, 4 - length_side)) ) * 5 ) ), s);
// 右侧
setfillcolor(color_num[box_start[i][j].num_right]);
p[0] = box_start[i][j].pos[1]; p[1] = box_start[i][j].pos[2]; p[2] = box_start[i][j].pos[4];
fillpolygon(p, 3);
setbkcolor(color_num[box_start[i][j].num_right]);
_stprintf_s(s, _T("%0d"), box_start[i][j].num_right);
outtextxy(box_start[i][j].pos[0].x + 180 / length_side + ( length_side <= 4 ? 10 * ( 6 - length_side ) : ( 11 - length_side ) * 3 ) + ( length_side == 4 ? 5 : 0 ),
box_start[i][j].pos[0].y + 180 / length_side - ( ( length_side >= 5 ? ( 8 - length_side ) * 5 : ( 2 + int(pow(2.0, 4 - length_side)) ) * 5 ) ), s);
}
// 空格子
else
{
setfillcolor(RGB(230, 230, 230));
setlinecolor(BLACK);
fillrectangle(box_start[i][j].pos[0].x, box_start[i][j].pos[0].y, box_start[i][j].pos[2].x, box_start[i][j].pos[2].y);
}
}
}
// 箭头
setfillcolor(RGB(230, 230, 230));
setlinecolor(WHITE);
p[0].x = 415; p[0].y = 20;
p[1].x = 415; p[1].y = 380;
p[2].x = 385; p[2].y = 200;
fillpolygon(p, 3);
// 按钮
settextstyle(25, 0, _T("Consolas"));
settextcolor(BLACK);
setbkcolor(RGB(220, 220, 220));
setfillcolor(RGB(220, 220, 220));
setlinecolor(WHITE);
for(i = 0; i < 6; i++)
{
// 边框
fillrectangle(box_button[i].posx1, box_button[i].posy1, box_button[i].posx2, box_button[i].posy2);
outtextxy(box_button[i].posx1 + 10, box_button[i].posy1 + 15, box_button[i].text);
}
// 已选中的格子
if(num_click != -1)
{
i = num_click % 15;
j = num_click / 15;
setlinecolor(WHITE);
setlinestyle(PS_SOLID, 6);
rectangle(box_start[i][j].pos[0].x, box_start[i][j].pos[0].y, box_start[i][j].pos[2].x, box_start[i][j].pos[2].y);
setlinestyle(PS_SOLID, 1);
setlinecolor(BLACK);
rectangle(box_start[i][j].pos[0].x + 3, box_start[i][j].pos[0].y + 3, box_start[i][j].pos[2].x - 3, box_start[i][j].pos[2].y - 3);
}
FlushBatchDraw();
}
// 提示函数
void Gary::tip()
{
int i, j, k, x, y, t;
// 提示数加一
num_tip++;
// 提示满了
if(num_tip == length_side * length_side)
{
MessageBox(hOut, _T("仅剩一块了"), _T("来自小豆子的提醒"), MB_OK);
num_tip--;
}
else
{
// 左移
for(i = length_side; i < length_side * 2; i++)
{
for(j = 0; j < length_side; j++)
{
// 右格不为空
if(box_start[i][j].num_type == 1)
{
for(x = 0; x < length_side; x++)
{
for(y = 0; y < length_side; y++)
{
// 左格为空
if(box_start[x][y].num_type == 0)
{
// 交换
exchange(i, j, x, y);
}
}
}
}
}
}
// 根据提示数右移
for(k = 0; k < num_tip; k++)
{
// zigzag 算法进行提示,3 次提示即可以确定一个角
t = k;
i = length_side;
j = length_side;
// 确定提示位置
while(i >= length_side || j >= length_side || box_start[i + length_side][j].num_type == 1)
{
for(i = 0; i < length_side * 2; i++)
{
if(i * ( i + 1 ) / 2 > t) { i--; break; }
}
j = t - i * ( i + 1 ) / 2;
i = i - j;
if(i >= length_side || j >= length_side || box_start[i + length_side][j].num_type == 1) { t++; }
}
// 搜寻
for(x = 0; x < length_side; x++)
{
for(y = 0; y < length_side; y++)
{
// 左格初始位置满足
if(box_start[x][y].num == i * 10 + j)
{
// 交换
exchange(i + length_side, j, x, y);
}
}
}
}
}
// 绘制
draw();
}
// 初始化函数
void Gary::initialization()
{
// 无选中
num_click = -1;
// 无提示
num_tip = 0;
setbkcolor(WHITE);
cleardevice();
srand((unsigned)time(NULL));
setlinecolor(BLACK);
// 颜色
color_num[0] = RGB(255, 255, 255);
color_num[1] = RGB(193, 125, 17);
color_num[2] = RGB(204, 0, 0);
color_num[3] = RGB(245, 121, 0);
color_num[4] = RGB(237, 212, 0);
color_num[5] = RGB(115, 210, 22);
color_num[6] = RGB(52, 101, 164);
color_num[7] = RGB(117, 80, 123);
color_num[8] = RGB(185, 185, 185);
color_num[9] = RGB(0, 245, 245);
int i, j, k;
// 格子
for(j = 0; j < length_side; j++)
{
// 左右两侧
for(i = 0; i < 2 * length_side; i++)
{
// 初始位置
box_start[i][j].num = i * 10 + j;
// 中心点
box_start[i][j].pos[0].x = 420 - 400 * ( i / length_side ) + ( 360 / length_side ) * ( i % length_side );
box_start[i][j].pos[0].y = 20 + ( 360 / length_side ) * j;
// 四个角
box_start[i][j].pos[1].x = box_start[i][j].pos[0].x + ( 360 / length_side );
box_start[i][j].pos[1].y = box_start[i][j].pos[0].y;
box_start[i][j].pos[2].x = box_start[i][j].pos[0].x + ( 360 / length_side );
box_start[i][j].pos[2].y = box_start[i][j].pos[0].y + ( 360 / length_side );
box_start[i][j].pos[3].x = box_start[i][j].pos[0].x;
box_start[i][j].pos[3].y = box_start[i][j].pos[0].y + ( 360 / length_side );
box_start[i][j].pos[4].x = box_start[i][j].pos[0].x + ( 180 / length_side );
box_start[i][j].pos[4].y = box_start[i][j].pos[0].y + ( 180 / length_side );
// 右侧随机生成
if(i < length_side)
{
// 顶部
if(j == 0) { box_start[i][j].num_top = rand() % num_color; }
else { box_start[i][j].num_top = box_start[i][j - 1].num_bottom; }
// 底部
box_start[i][j].num_bottom = rand() % num_color;
// 左侧
if(i == 0) { box_start[i][j].num_left = rand() % num_color; }
else { box_start[i][j].num_left = box_start[i - 1][j].num_right; }
// 右侧
box_start[i][j].num_right = rand() % num_color;
// 状态
box_start[i][j].num_type = 1;
}
// 左侧为空
else
{
box_start[i][j].num_type = 0;
}
}
}
// 打乱
for(k = 0; k < length_side * length_side; k++)
{
i = rand() % ( length_side * length_side );
exchange(k % length_side, k / length_side, i % length_side, i / length_side);
}
// 按钮
box_button[0].posx1 = 20; box_button[0].posy1 = 400; box_button[0].text = _T("新开");
box_button[0].posx2 = box_button[0].posx1 + 65; box_button[0].posy2 = box_button[0].posy1 + 60;
box_button[1].posx1 = 140; box_button[1].posy1 = 400; box_button[1].text = _T("复原");
box_button[1].posx2 = box_button[1].posx1 + 65; box_button[1].posy2 = box_button[1].posy1 + 60;
box_button[2].posx1 = 260; box_button[2].posy1 = 400; box_button[2].text = _T("提示");
box_button[2].posx2 = box_button[2].posx1 + 65; box_button[2].posy2 = box_button[2].posy1 + 60;
box_button[3].posx1 = 475; box_button[3].posy1 = 400; box_button[3].text = _T("边长");
box_button[3].posx2 = box_button[3].posx1 + 65; box_button[3].posy2 = box_button[3].posy1 + 60;
box_button[4].posx1 = 595; box_button[4].posy1 = 400; box_button[4].text = _T("颜色");
box_button[4].posx2 = box_button[4].posx1 + 65; box_button[4].posy2 = box_button[4].posy1 + 60;
box_button[5].posx1 = 715; box_button[5].posy1 = 400; box_button[5].text = _T("退出");
box_button[5].posx2 = box_button[5].posx1 + 65; box_button[5].posy2 = box_button[5].posy1 + 60;
// 绘制
draw();
// 时间重置
start_t1 = clock();
start_t2 = clock();
}
// 窗口主视角函数
void Gary::move()
{
int i, j, k, x, y;
// 主视角控量
exit_move = 0;
// 主视角
while(exit_move == 0)
{
// 时间
time_new();
if(peekmessage(&m, EM_MOUSE | EM_KEY))
{
// 左键单击判断
if(m.message == WM_LBUTTONDOWN)
{
i = ( m.x > 420 ? 1 : 0 ) * ( m.x - 420 ) / ( 360 / length_side ) + ( m.x < 380 ? 1 : 0 ) * ( ( m.x - 20 ) / ( 360 / length_side ) + length_side );
j = ( m.y - 20 ) / ( 360 / length_side );
// 点击格子
if(( ( m.x > 420 && m.x < 780 ) || ( m.x > 20 && m.x < 380 ) ) && m.y < 380 && m.y>20 && box_start[i][j].num_type == 1)
{
// 记录
num_click = i + j * 15;
// 绘制
draw();
// 二次点击
while(true)
{
time_new();
if(peekmessage(&m, EM_MOUSE | EM_KEY))
{
// 左键单击判断
if(m.message == WM_LBUTTONDOWN)
{
// 点击格子
if(( ( m.x > 420 && m.x < 780 ) || ( m.x > 20 && m.x < 380 ) ) && m.y < 380 && m.y>20)
{
x = ( m.x > 420 ? 1 : 0 ) * ( m.x - 420 ) / ( 360 / length_side ) + ( m.x < 380 ? 1 : 0 ) * ( ( m.x - 20 ) / ( 360 / length_side ) + length_side );
y = ( m.y - 20 ) / ( 360 / length_side );
break;
}
// 判断是否点击了按钮
else
{
for(k = 0; k < 6; k++)
{
// 矩形按钮
if(m.x > box_button[k].posx1 && m.y > box_button[k].posy1 && m.x < box_button[k].posx2 && m.y < box_button[k].posy2)
{
// 记录
num_click = -1;
// 绘制
draw();
// 返回
goto A;
}
}
}
}
}
}
// 记录
num_click = -1;
// 两次点击
if(i != x || j != y)
{
// 交换
exchange(i, j, x, y);
// 检查
check(i, j, x, y);
// 判定
end();
}
// 绘制
draw();
}
// 判断是否点击了按钮
else
{
for(k = 0; k < 6; k++)
{
// 矩形按钮
if(m.x > box_button[k].posx1 && m.y > box_button[k].posy1 && m.x < box_button[k].posx2 && m.y < box_button[k].posy2)
{
break;
}
}
A:
switch(k)
{
// 新开
case 0:exit_move = 1; break;
// 复原
case 1:solve(); break;
// 提示
case 2:tip(); break;
// 边长
case 3:
{
InputBox(s, 10, _T("输入边长(2 ~ 6)"));
_stscanf_s(s, _T("%d"), &i);
if(i >= 2 && i <= 6)
{
length_side = int(i);
exit_move = 1;
}
else
{
MessageBox(hOut, _T("输入错误,不在范围内"), _T("来自小豆子的提醒"), MB_OK);
}
break;
}
// 颜色
case 4:
{
InputBox(s, 10, _T("输入颜色(2 ~ 10)"));
_stscanf_s(s, _T("%d"), &i);
if(i >= 2 && i <= 10)
{
num_color = int(i);
exit_move = 1;
}
else
{
MessageBox(hOut, _T("输入错误,不在范围内"), _T("来自小豆子的提醒"), MB_OK);
}
break;
}
// 退出
case 5:exit_move = 1; exit_carry = 1; break;
default:break;
}
}
}
}
}
}
// 主进程
void Gary::carry()
{
// 窗口定义
hOut = initgraph(800, 480);
SetWindowText(hOut, _T("四邻"));
// 进程控制
exit_carry = 0;
BeginBatchDraw();
// 初始难度设定,边长 4,颜色 10
length_side = 4;
num_color = 10;
while(exit_carry == 0)
{
initialization();
move();
}
EndBatchDraw();
closegraph();
}
// 主函数
int main(void)
{
Gary G;
G.carry();
return 0;
}