命令行之连连看
程序实践周课题,VC++6.0上可编译运行
游戏截图:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <windows.h>
#include <time.h>
#include <algorithm>
using namespace std;
int dirx[4] = {1,-1,0,0};
int diry[4] = {0,0,1,-1}; //四个方向,DFS函数中要使用
bool vis[15][15]; //标记该点是否已经被访问,DFS中使用
char map[15][15]; //二维字符数组表示连连看矩阵
bool Hash[105]; //hash数组用来标记,去重
int num, level; //num游戏记录人数, level游戏等级
char choose[100]; //提取相应操作的字符
struct Players //结构体存游戏者的帐号,得分,排名
{
char name[50];
int score;
int rank;
};
struct Data
{
int n;
struct Players player[100];
}data[4];
bool cmp(Players a, Players b)
{
return a.score < b.score;
}
void set_rank(int l)
{
int tmp;
if (l == 4)
tmp = 0;
else if (l == 6)
tmp = 1;
else if (l == 8)
tmp = 2;
else if (l == 10)
tmp = 3;
for(int i = 0; i < data[tmp].n; i++)
{
data[tmp].player[i].rank = i + 1;
if(data[tmp].player[i].score == data[tmp].player[i + 1].score)
{
data[tmp].player[i + 1].rank = data[tmp].player[i].rank;
i++;
}
}
}
void menu() //菜单
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN);
printf("\n\n *********************************\n");
SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED);
printf(" ***** 欢迎进入 连连看 *****\n");
SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN);
printf(" ***** C - 选择游戏等级 *****\n");
printf(" ***** T - 有时间限定 *****\n");
printf(" ***** N - 无时间限定 *****\n");
printf(" ***** R - 玩家排名 *****\n");
printf(" ***** E - 游戏结束 *****\n");
printf(" *********************************\n\n");
if(level == 0)
printf(" ***** 游戏等级 : 未选 *****\n");
else if(level == 4)
printf(" ***** 游戏等级 : 低级 *****\n");
else if(level == 6)
printf(" ***** 游戏等级 : 中级 *****\n");
else if(level == 8)
printf(" ***** 游戏等级 : 高级 *****\n");
else if(level == 10)
printf(" ***** 游戏等级 : 特高 *****\n");
printf("\n输入操作 : ");
scanf("%s", &choose);
//printf("choose = %c\n", choose);
//system("PAUSE");
system("CLS"); //清屏函数
//清屏后显示菜单
}
void ChooseMenu()
{
int tmp;
//设4个等级,通过选择游戏难度初始化level
printf("\n\n ********************************\n");
printf(" ***** 选择等级 *****\n");
printf(" ***** 1 - 低级 *****\n"); // 4 * 4
printf(" ***** 2 - 中级 *****\n"); // 6 * 6
printf(" ***** 3 - 高级 *****\n"); // 8 * 8
printf(" ***** 4 - 特高 *****\n"); // 10 * 10
printf(" ********************************\n");
printf(" \n游戏等级 : ");
scanf("%d", &tmp);
while(tmp != 1 && tmp != 2 && tmp != 3 && tmp != 4)
{
printf(" 请选择正确的游戏等级 1 , 2 , 3 , 4\n");
printf(" \n游戏等级 : ");
scanf("%d", &tmp);
}
if (tmp == 1)
level = 4;
else if (tmp == 2)
level = 6;
else if (tmp == 3)
level = 8;
else if (tmp == 4)
level = 10;
getchar();
}
void init_map() //初始化游戏矩阵
{
int i, j;
char get[105]; //get数组用来得到随机字符
memset(Hash, false, sizeof(Hash)); //初始化hash数组
srand((unsigned)time(NULL)); //设置随机数种子
//随机生成一半的字符,因为要保证两两配对
for(i = 0; i < (level * level) / 2; i++)
{
get[i] = 65 + rand() % 26; //随机生成前一半
get[i + (level * level) / 2] = get[i]; //将前一半的值赋给后一半
}
int index; //在get中随机产生的数组下标
bool flag; //标记是否找到一个新的字符
srand((unsigned)time(NULL)); //设置随机数种子
for(i = 1; i <= level; i++)
{
for(j = 1; j <= level; j++)
{
flag = false; //flag每次先赋值为false
while(true) //死循环
{
index = 0 + rand() % (level * level); //随机生成get数组中的下标
if(!Hash[index]) // 如果该点没有被使用
{
map[i][j] = get[index]; //将它的值赋给map
Hash[index] = true; //将它标记为已使用
flag = true; //找到一个字符flag设为true
}
if(flag) //如果找到则退出for循环
break;
}
}
}
//在连连看矩阵周围加一圈空格,用来消除边界元素
for(i = 0; i <= level + 1; i++)
map[0][i] = map[level + 1][i] = map[i][0] = map[i][level +1] = ' ';
}
void show_map() //显示矩阵
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
printf("\n\n\n");
int i, j;
SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED);
printf(" ");
for(i = 1; i <= level; i++)
printf("%4d", i);
SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_GREEN);
printf(" 游戏选择");
printf("\n\n");
for(i = 1; i <= level; i++)
{
SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN);
printf(" ");
for(j = 1; j <= level; j++)
printf("%4c", map[i][j]);
SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED);
printf("%4d", i);
SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_GREEN);
if (i == 1)
printf(" T - 提示\n\n");
else if (i == 2)
printf(" R - 洗牌\n\n");
else if (i == 3)
printf(" C - 继续\n\n");
else
printf("\n\n");
}
SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_GREEN);
}
bool IsWin() //判断是否结束
{
int i, j;
for(i = 1; i <= level; i++)
for(j = 1; j <= level; j++)
if(map[i][j] != ' ') //如果矩阵中有一个不是空格则说明没有结束
return false;
return true;
}
bool DFS(int x1, int y1, int x2, int y2, int turn, int dir)
{
//转向次数大于2则返回false,这里大于3是因为dir初始为-1也就是第一次选方向时
//就已经让turn加1了,
if(turn > 3)
return false;
//转向两次后不能沿某一方向到终点,返回false
if(turn == 3 && (x2 != x1 && y2 != y1))
return false;
//到达终点,返回true
if(x1 == x2 && y1 == y2)
return true;
for(int i = 0; i < 4; i++) //四个方向遍历
{
int xx = x1 + dirx[i];
int yy = y1 + diry[i];
//若该点有字符且不是终点则换方向
if(map[xx][yy] != ' ' && !(xx == x2 && yy == y2))
continue;
//判断是否出界,是否已经访问
if(xx < 0 || yy < 0 || xx > level + 1 || yy > level + 1 || vis[xx][yy])
continue;
//将该点设为已访问
vis[xx][yy] = true;
//若当前方向与之前方向不同,转向次数+1,继续搜索
if(DFS(xx, yy, x2, y2, turn + (dir != i), i))
return true;
//若从该点的dfs不成功,将该点设为未访问
vis[xx][yy] = false;
}
return false;
}
bool judge(int x1, int y1, int x2, int y2)
{
memset(vis, false, sizeof(vis)); //初始化vis数组用于DFS
vis[x1][y1] = true; //将起点标记为已访问
//特判几种不能消除的情况
if(x1 == x2 && y1 == y2)
{
printf("\n 输入的两点坐标相同,请保证两点的坐标不同\n\n");
return false;
}
if(map[x1][y1] == ' ' || map[x2][y2] == ' ')
{
printf("\n 两点之中有没有图案的点,请保证两点都有图案\n\n");
return false;
}
if(map[x1][y1] != map[x2][y2])
{
printf("\n 两点图案不同,请保证两点图案相同\n\n");
return false;
}
if(DFS(x1, y1, x2, y2, 0, -1))
{
printf("\n 这两点可以消除\n\n");
return true;
}
else
{
printf("这两点不能消除,请重试\n\n");
return false;
}
}
//该函数在total_judge()中被调用,功能与judge类似
//不输出中文提示
bool judge2(int x1, int y1, int x2, int y2)
{
memset(vis, false, sizeof(vis));
vis[x1][y1] = true;
if(x1 == x2 && y1 == y2)
return false;
if(map[x1][y1] == ' ' || map[x2][y2] == ' ')
return false;
if(map[x1][y1] != map[x2][y2])
return false;
if(DFS(x1, y1, x2, y2, 0, -1))
return true;
return false;
}
bool total_judge() //全盘判定,用于判断是否还有可行解
{
int i, j, ii, jj;
bool vis2[15][15]; //标记该点是否被访问
memset(vis2, false, sizeof(vis2)); //初始化vis2
int x1, y1, x2, y2; //记录两个点的坐标
for(i = 1; i <= level; i++)
{
for(j = 1; j <= level; j++)
{
//如果该点不是空格,并且没有被访问
if(map[i][j] != ' ' && !vis2[i][j])
{
vis2[i][j] = true; //将其设为已访问
//找到一个点A赋值
x1 = i;
y1 = j;
//printf("x1 = %d y1 = %d\n", x1, y1);
//枚举点找到与A点字符相同的一个点
for(ii = 1; ii <= level; ii++)
{
for(jj = 1; jj <= level; jj++)
{
if(map[ii][jj] == map[x1][y1] && !vis2[ii][jj])
{
//如果找到一个点与A点不是同一点并且图案相同
//将其标记为已访问
vis2[ii][jj] = true;
//记为B点并赋值
x2 = ii;
y2 = jj;
//printf("x2 = %d y2 = %d\n", x2, y2);
//判断A,B能否被消除
//找到一个可行解则说明游戏可以继续不用restart
if(judge2(x1, y1, x2, y2))
return true;
}
}
}
}
}
}
//如果没找到可行解,则需要restart
return false;
}
bool Tip() //全盘判定,用于判断是否还有可行解
{
int i, j, ii, jj;
bool vis2[15][15]; //标记该点是否被访问
memset(vis2, false, sizeof(vis2)); //初始化vis2
int x1, y1, x2, y2; //记录两个点的坐标
for(i = 1; i <= level; i++)
{
for(j = 1; j <= level; j++)
{
//如果该点不是空格,并且没有被访问
if(map[i][j] != ' ' && !vis2[i][j])
{
vis2[i][j] = true; //将其设为已访问
//找到一个点A赋值
x1 = i;
y1 = j;
//枚举点找到与A点字符相同的一个点
for(ii = 1; ii <= level; ii++)
{
for(jj = 1; jj <= level; jj++)
{
if(map[ii][jj] == map[x1][y1] && !vis2[ii][jj])
{
//如果找到一个点与A点不是同一点并且图案相同
//将其标记为已访问
vis2[ii][jj] = true;
//记为B点并赋值
x2 = ii;
y2 = jj;
//判断A,B能否被消除
//找到一个可行解则说明游戏可以继续不用restart
if(judge2(x1, y1, x2, y2))
{
//输出可行解
printf("\n 提示 :\n x1 = %d, y1 = %d\n x2 = %d, y2 = %d\n", x1, y1, x2, y2);
return true;
}
}
}
}
}
}
}
return false;
}
void Restart() //洗牌函数
{
int i, j;
bool hash2[105], flag;
char re[105];
int cnt = 0, index2;
memset(re, 0, sizeof(re));
memset(hash2, false, sizeof(hash2));
for(i = 1; i <= level; i++)
for(j = 1; j <= level; j++)
if(map[i][j] != ' ')
re[cnt++] = map[i][j]; //提取有用字符
srand((unsigned)time(NULL));
for(i = 1; i <= level; i++)
{
for(j = 1; j <= level; j++)
{
if (map[i][j] != ' ')
{
flag = false;
while (true)
{
index2 = 0 + rand() % cnt;
if (!hash2[index2])
{
hash2[index2] = true;
map[i][j] = re[index2];
flag = true;
}
if (flag)
break;
}
}
}
}
}
void Rank()
{
printf("\n\n");
for (int i = 0; i < 4; i++)
{
if (i == 0)
{
printf(" 低级:\n");
printf(" 排名 账号 成绩(秒)\n");
for (int j = 0; j < data[i].n; j++)
printf(" %d %s %d\n", data[i].player[j].rank, data[i].player[j].name, data[i].player[j].score / 1000);
printf("\n");
}
if (i == 1)
{
printf(" 中级:\n");
printf(" 排名 账号 成绩(秒)\n");
for (int j = 0; j < data[i].n; j++)
printf(" %d %s %d\n", data[i].player[j].rank, data[i].player[j].name, data[i].player[j].score / 1000);
printf("\n");
}
if (i == 2)
{
printf(" 高级:\n");
printf(" 排名 账号 成绩(秒)\n");
for (int j = 0; j < data[i].n; j++)
printf(" %d %s %d\n", data[i].player[j].rank, data[i].player[j].name, data[i].player[j].score / 1000);
printf("\n");
}
if (i == 3)
{
printf(" 特高:\n");
printf(" 排名 账号 成绩(秒)\n");
for (int j = 0; j < data[i].n; j++)
printf(" %d %s %d\n", data[i].player[j].rank, data[i].player[j].name, data[i].player[j].score / 1000);
printf("\n");
}
}
system("PAUSE");
}
int GameMenuChoose()
{
printf("\n 输入操作 : ");
char tmp;
getchar();
scanf("%c", &tmp);
if (tmp == 'T')
{
Tip();
return 1;
}
if (tmp == 'R')
{
system("CLS");
Restart();
show_map();
return 1;
}
if (tmp == 'C')
return 0;
return 0;
}
void play() //游戏函数
{
int x1, y1, x2, y2;
while (GameMenuChoose());
printf("\n 请输入两点坐标\n");
printf("\n 一 : ");
scanf("%d %d", &x1, &y1);
printf("\n 二 : ");
scanf("%d %d", &x2, &y2);
if (judge(x1, y1, x2, y2)) //如果可以消除,把两点的值设为空格
{
map[x1][y1] = ' ';
map[x2][y2] = ' ';
}
}
void Start()
{
system("CLS");
init_map(); //初始化游戏界面
while (!IsWin()) //如果游戏没有结束则一直play
{
while (!total_judge()) //若没有可行解,则一直洗牌直到有可行解
{
Restart();
show_map();
}
show_map();
play();
system("PAUSE");
system("CLS");
}
system("CLS");
printf("Good Job!\n");
system("PAUSE");
system("CLS");
}
void ReadInMemory(Data *u)
{
FILE *fp;
fp = fopen("data.txt", "rb");
if (fp == NULL)
return;
fread(u, sizeof(struct Data), 1, fp);
fclose(fp);
}
void WriteToFile(Data * u)
{
FILE *fp;
fp = fopen("data.txt", "wb");
if (fp == NULL)
return;
fwrite(u, sizeof(struct Data), 1, fp);
fclose(fp);
}
void SetData(int l,int e, int s, char *a)
{
if (l == 4)
{
data[0].n++;
data[0].player[data[0].n].score = (int)e - s;
strcpy(data[0].player[data[0].n].name, a);
sort(data[0].player, data[0].player + data[0].n, cmp);
}
else if (l == 6)
{
data[1].n++;
data[1].player[data[1].n].score = (int)e - s;
strcpy(data[1].player[data[1].n].name, a);
sort(data[1].player, data[1].player + data[1].n, cmp);
}
else if (l == 8)
{
data[2].n++;
data[2].player[data[2].n].score = (int)e - s;
strcpy(data[2].player[data[2].n].name, a);
sort(data[2].player, data[2].player + data[2].n, cmp);
}
else if (l == 10)
{
data[3].n++;
data[3].player[data[3].n].score = (int)e - s;
strcpy(data[3].player[data[3].n].name, a);
sort(data[3].player, data[3].player + data[3].n, cmp);
}
}
int main()
{
//system("title 连连看");
level = 0;
Loop:
char tmp_name[20];
ReadInMemory(data);
menu();
if(choose[0] == 'E')
return 0;
if(choose[0] == 'R')
{
Rank();
system("CLS");
goto Loop;
}
if(choose[0] == 'C')
{
system("CLS");
ChooseMenu();
system("PAUSE");
system("CLS");
goto Loop;
}
if (level == 0 && (choose[0] == 'N' || choose[0] == 'T'))
{
while (choose[0] != 'C')
{
system("CLS");
printf("\n\n 请先选择游戏等级\n");
ChooseMenu();
system("PAUSE");
if (level != 0)
{
system("CLS");
break;
}
}
goto Loop;
}
if (choose[0] == 'N')
{
Start();
goto Loop;
}
if (choose[0] == 'T')
{
printf("请输入用户名\n");
cin >> tmp_name;
clock_t st = clock();
Start();
clock_t ed = clock();
SetData(level, st, ed, tmp_name);
set_rank(level);
WriteToFile(data);
goto Loop;
}
if(strlen(choose) != 1 || choose[0] != 'E' || choose[0] != 'R' || choose[0] != 'C' || choose[0] != 'N' || choose[0] != 'T')
{
printf("非法输入,请看清菜单选项\n");
system("PAUSE");
system("CLS");
goto Loop;
}
return 0;
}