C语言两种鼠标操作与扫雷实例
大家好,这一篇博客讲的是我对C语言两种鼠标操作的理解和经验总结。如果有错误,还请读者大大们指出。
这里声明:
本人对获取的鼠标代码并不了解,只是发现了其部分的作用,不确定会不会有异常的bug,所以这篇文章的内容仅供参考。
扫雷游戏实例展示:
鼠标程序展示6
首先准备 game.h头文件、test.c源文件、game.c源文件。
第一种鼠标操作
鼠标操作需要的代码
//定义句柄
HANDLE in_put = NULL;
//用于存储鼠标当前坐标
COORD pos = { 0,0 };
//定义输入事件结构体
INPUT_RECORD mouse_record;
//用于存储读取记录
DWORD res;
in_put = GetStdHandle(STD_INPUT_HANDLE);
DWORD fdwMode;
fdwMode = ENABLE_EXTENDED_FLAGS | ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT;
if (!SetConsoleMode(in_put, fdwMode))
fprintf(stderr, "%s\n", "SetConsoleMode");
这串代码我是从其他C++文章中提取出的,出处忘记了,如果读者在其他地方看到这串代码,请联系我其出处,我会补上链接。
Bug提醒
system(“cls”)刷屏导致鼠标失效
if (!SetConsoleMode(in_put, fdwMode))
fprintf(stderr, "%s\n", "SetConsoleMode");
这段代码将 “SetConsoleMode” 字符串存入 stderr 以获得鼠标操作的(原理我也不知道),就不用玩家手动去更改。(手动更改快速编辑链接提供:windows 命令行快速编辑模式 关闭)
不过使用system(“cls”)时好像会删掉 这个字符串,使鼠标操作失效。我的方法是使用system(“cls”)后就再次使用上面的代码。
键盘读取字符导致鼠标操作异常
在鼠标操作时,用scanf函数、getch函数等键盘输入字符会使程序读取鼠标信息异常,此时输入字符前的鼠标信息会被保留。当鼠标到达操作区域时下一次鼠标操作还未开始,会一直执行被保留的鼠标信息,直到下一次鼠标信息接收才停止异常状态。这个我没有什么好的解决方法。
代码操作
界面展示
在 game.h头文件中:
#pragma once
#include <stdio.h>
#include <Windows.h>
void gotoxy(int a, int b);
在 test.c源文件中:
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
//定义句柄变量
HANDLE get1;
//定义输入事件结构体
INPUT_RECORD mouseRecord;
//用于存储读取记录
DWORD res;
void gotoxy(int a, int b) // 固定画面位置
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos;
pos.X = a;
pos.Y = b;
SetConsoleCursorPosition(handle, pos);
}
void HideCursor() // 隐藏光标
{
CONSOLE_CURSOR_INFO cursor_info = { 1, 0 };
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}
void getMouseOperate()
{
if (!SetConsoleMode(get1, ENABLE_EXTENDED_FLAGS | ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT))
fprintf(stderr, "%s\n", "SetConsoleMode");
}
void menu()
{
getMouseOperate();
int y = 0;
int x = 0;
while (1)
{
gotoxy(0, 0); // 固定画面
//读取输入事件
ReadConsoleInput(get1, &mouseRecord, 1, &res);
//获取鼠标当前位置
y = mouseRecord.Event.MouseEvent.dwMousePosition.Y;
x = mouseRecord.Event.MouseEvent.dwMousePosition.X;
char arr[2][13] = { "开始游戏", "退出游戏" };
printf("********************\n");
// 对应鼠标坐标与数组
if ((y == 1) && (4 <= x && x <= 15))
printf("****\033[41m%12s\033[0m****\n", &arr[0][0]);
else
printf("****%-12s****\n", &arr[0][0]);
if ((y == 2) && (4 <= x && x <= 15))
printf("****\033[41m%12s\033[0m****\n", &arr[1][0]);
else
printf("****%-12s****\n", &arr[1][0]);
printf("********************\n");
Sleep(50);
}
}
int main()
{
get1 = GetStdHandle(STD_INPUT_HANDLE); // 得到标准输入句柄
HideCursor(); // 隐藏光标
menu(); // 菜单选择
CloseHandle(get1); // 关闭
return 0;
}
程序演示
鼠标程序展示1
完善界面操作与游戏执行
在 game.h头文件中:
#pragma once
#define HIGH 5 // 设置游戏画面高度
#define WIGHT 5 // 设置游戏画面宽度
#include <stdio.h>
#include <Windows.h>
void gotoxy(int a, int b);
void game();
void getMouseOperate();
在 test.c源文件中:
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
//定义句柄变量
HANDLE get1;
//定义输入事件结构体
INPUT_RECORD mouseRecord;
//用于存储读取记录
DWORD res;
void gotoxy(int a, int b) // 固定画面位置
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos;
pos.X = a;
pos.Y = b;
SetConsoleCursorPosition(handle, pos);
}
void HideCursor() // 隐藏光标
{
CONSOLE_CURSOR_INFO cursor_info = { 1, 0 };
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}
void getMouseOperate()
{
if (!SetConsoleMode(get1, ENABLE_EXTENDED_FLAGS | ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT))
fprintf(stderr, "%s\n", "SetConsoleMode");
}
void mouseOperate(int y, int x, int mouseChoose)
{
switch (mouseChoose)
{
case FROM_LEFT_1ST_BUTTON_PRESSED: // 左键
if ((y == 1) && (4 <= x && x <= 15))
{
system("cls");
game();
}
if ((y == 2) && (4 <= x && x <= 15))
{
system("cls");
printf("退出游戏\n");
exit(0);
}
}
}
void menu()
{
getMouseOperate();
int y = 0;
int x = 0;
while (1)
{
gotoxy(0, 0); // 固定画面
//读取输入事件
ReadConsoleInput(get1, &mouseRecord, 1, &res);
//获取鼠标当前位置
y = mouseRecord.Event.MouseEvent.dwMousePosition.Y;
x = mouseRecord.Event.MouseEvent.dwMousePosition.X;
char arr[2][13] = { "开始游戏", "退出游戏" };
printf("********************\n");
// 对应鼠标坐标与数组
if ((y == 1) && (4 <= x && x <= 15))
printf("****\033[41m%12s\033[0m****\n", &arr[0][0]);
else
printf("****%-12s****\n", &arr[0][0]);
if ((y == 2) && (4 <= x && x <= 15))
printf("****\033[41m%12s\033[0m****\n", &arr[1][0]);
else
printf("****%-12s****\n", &arr[1][0]);
printf("********************\n");
//获取鼠标操作
int mouseChoose = mouseRecord.Event.MouseEvent.dwButtonState;
mouseOperate(y, x, mouseChoose);
Sleep(50);
}
}
int main()
{
get1 = GetStdHandle(STD_INPUT_HANDLE); // 得到标准输入句柄
HideCursor(); // 隐藏光标
menu(); // 菜单选择
CloseHandle(get1); // 关闭
return 0;
}
在 game.c源文件中:
#include "game.h"
extern HANDLE get1;
extern INPUT_RECORD mouseRecord;
extern DWORD res;
void init(char arr[HIGH][WIGHT])
{
for (int i = 0; i < HIGH; i++)
for (int j = 0; j < WIGHT; j++)
arr[i][j] = '*';
}
void game()
{
getMouseOperate(); // system("cls")使用后记得重新获取
char arr[HIGH][WIGHT] = { 0 };
init(arr);
int y = 0;
int x = 0;
while (1)
{
gotoxy(0, 0);
//读取输入事件
ReadConsoleInput(get1, &mouseRecord, 1, &res);
//获取鼠标当前位置
y = mouseRecord.Event.MouseEvent.dwMousePosition.Y;
x = mouseRecord.Event.MouseEvent.dwMousePosition.X;
//获取鼠标操作
int mouseChoose = mouseRecord.Event.MouseEvent.dwButtonState;
for (int i = 0; i < HIGH; i++)
{
for (int j = 0; j < WIGHT; j++)
{
if (i == y && j * 2 == x)
{
printf("\033[42m%c\033[0m ", arr[i][j]); // 背景色绿色
// 将鼠标操作放入打印内
if (mouseChoose == FROM_LEFT_1ST_BUTTON_PRESSED) // 左键
{
if (arr[i][j] == '*')
arr[i][j] = '#';
}
if (mouseChoose == RIGHTMOST_BUTTON_PRESSED) // 右键
{
if (arr[i][j] == '#')
arr[i][j] = '*';
}
}
else
{
printf("%c ", arr[i][j]);
}
}
printf("\n");
}
// 返回界面
printf("**************\n");
if ((y == 6) && (4 <= x && x <= 9))
{
printf("****\033[41m%6s\033[0m****\n", "返回");
if (mouseChoose == FROM_LEFT_1ST_BUTTON_PRESSED) // 左键
{
system("cls");
getMouseOperate(); // 记得重新获取
break;
}
}
else
{
printf("****%-6s****\n", "返回");
}
printf("**************\n");
Sleep(50);
}
}
程序演示:
鼠标程序展示2
第二种鼠标操作
鼠标操作需要的代码
void MouseOpreate(int *x,int *y){
POINT *ptrpos;//声明POINT结构体类存储鼠标坐标
LPRECT rect;//声明LPRECT结构体指针存储窗口坐标信息
HWND hwnd=FindWindow(NULL,"憨憨");//根据上述设定的标题查找句柄,第一个形参设为NULL意味着查找系统中所有句柄
ptrpos=(POINT*)malloc(sizeof(POINT)+64);
rect=(LPRECT*)malloc(sizeof(LPRECT)+64);//为结构体指针分配内存空间
for(;;){
Sleep(500);//通过休眠指定循环周期为500毫秒
if(GetAsyncKeyState(VK_LBUTTON)){//如果鼠标左键按下,其中VK_LBUTTON是windows.h库中定义的宏,对应鼠标左键对应的ASCII值
GetWindowRect(hwnd,rect);//根据查找标题"憨憨"得到的句柄返回对于窗口的坐标并存储在rect中
GetCursorPos(ptrpos);//返回鼠标对应的坐标并存储在ptrpos中
*x=ptrpos->x-rect->left;//鼠标横坐标-控制台窗口左端横坐标得到鼠标相对控制台窗口的横坐标
*y=ptrpos->y-rect->top;//同理得到鼠标相对的纵坐标
break;//得到坐标后跳出循环
}
}
这段代码截取于这篇博客C语言在控制台上实现鼠标操作的方法
不过它不是以方格的方式标记控制台的坐标,我们需要对鼠标坐标进行方格化。
鼠标方格化
鼠标方格化是将鼠标坐标转化为一个个的方格,方便后续的操作。
初步观察
这里我们需要自己手动去计算,VS2022控制台默认的一个方格高18、长10,则根据观察总结,写出下面的代码:
//鼠标坐标方块化
// 方块化x坐标
int findX = (x - 14) / 10;
// 方块化y坐标
int findY = (y - 51) / 18;
这是因为:
第一个方格的x轴要减14,y轴要减51,因为不属于方格的范围,而x除以10,y除以18刚好是一个方格的位置。
进行鼠标测试:
在 game.h头文件中:
#pragma once
#include <stdio.h>
#include <Windows.h>
#define HIGH 30 // 设置游戏画面高度
#define WIGHT 50 // 设置游戏画面宽度
void gotoxy(int a, int b);
在 test.c源文件中:
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
//声明POINT结构体类存储鼠标坐标
POINT* ptrpos;
//声明LPRECT结构体指针存储窗口坐标信息
LPRECT rect;
HWND hwnd;
void gotoxy(int a, int b) // 固定画面位置
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos;
pos.X = a;
pos.Y = b;
SetConsoleCursorPosition(handle, pos);
}
void HideCursor() // 隐藏光标
{
CONSOLE_CURSOR_INFO cursor_info = { 1, 0 };
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}
void MouseOperate(int* x, int* y)
{
//根据上述设定的标题查找句柄,第一个形参设为NULL意味着查找系统中所有句柄
hwnd = FindWindow(NULL, "憨憨");
//根据查找标题"憨憨"得到的句柄返回对于窗口的坐标并存储在rect中
GetWindowRect(hwnd, rect);
//返回鼠标对应的坐标并存储在ptrpos中
GetCursorPos(ptrpos);
*x = ptrpos->x - rect->left;//鼠标横坐标-控制台窗口左端横坐标得到鼠标相对控制台窗口的横坐标
*y = ptrpos->y - rect->top;//同理得到鼠标相对的纵坐标
//鼠标坐标方块化
// 方块化x坐标
*x = (*x - 14) / 10;
// 方块化y坐标
*y = (*y - 51) / 18;
}
void init(char arr[HIGH][WIGHT])
{
for (int i = 0; i < HIGH; i++)
for (int j = 0; j < WIGHT; j++)
arr[i][j] = '*';
}
void menu()
{
char arr[HIGH][WIGHT] = { 0 };
init(arr);
int y = 0;
int x = 0;
while (1)
{
gotoxy(0, 0); // 固定画面
//获取鼠标当前位置
MouseOperate(&x, &y);
for (int i = 0; i < HIGH; i++)
{
for (int j = 0; j < WIGHT; j++)
{
if (i == y && j == x)
{
printf("\033[42m%c\033[0m", arr[i][j]); // 背景色绿色
}
else
{
printf("%c", arr[i][j]);
}
}
printf("\n");
}
}
}
int main()
{
//为结构体指针分配内存空间
ptrpos = (POINT*)malloc(sizeof(POINT) + 64);
rect = (LPRECT*)malloc(sizeof(LPRECT) + 64);
SetConsoleTitle("憨憨");//设定窗口标题
HideCursor();// 隐藏光标
menu();// 菜单选择
free(ptrpos);
free(rect);
return 0;
}
程序演示:
鼠标程序展示3
这里发现随着鼠标移动和x,y的增大,鼠标坐标与实际数组坐标误差也会越来越大,这个原因我不清楚,但是我们可以手动的进行 减小误差。
减小误差
通过不断观察,发现这个误差似乎是线性增长的,则我们可以写下这段代码:
//鼠标坐标方块化
// 减小x坐标误差
int fixX = (x - 14) / 10;
x = x + fixX * 0.4;
// 方块化x坐标
int findX = (x - 14) / 10;
// 减小y坐标误差
int fixY = (y - 51) / 18;
y = y - fixY * 0.9;
// 方块化y坐标
int findY = (y - 51) / 18;
再次测试:
在 test.c源文件中:
void MouseOperate(int* x, int* y)
{
//根据上述设定的标题查找句柄,第一个形参设为NULL意味着查找系统中所有句柄
hwnd = FindWindow(NULL, "憨憨");
//根据查找标题"憨憨"得到的句柄返回对于窗口的坐标并存储在rect中
GetWindowRect(hwnd, rect);
//返回鼠标对应的坐标并存储在ptrpos中
GetCursorPos(ptrpos);
*x = ptrpos->x - rect->left;//鼠标横坐标-控制台窗口左端横坐标得到鼠标相对控制台窗口的横坐标
*y = ptrpos->y - rect->top;//同理得到鼠标相对的纵坐标
//鼠标坐标方块化
// 减小x坐标误差
int fixX = (*x - 14) / 10;
*x = (int)(*x + fixX * 0.4);
// 方块化x坐标
*x = (*x - 14) / 10;
// 减小y坐标误差
int fixY = (*y - 51) / 18;
*y = (int)(*y - fixY * 0.9);
// 方块化y坐标
*y = (*y - 51) / 18;
}
鼠标程序展示4
经过我个人的测试(在 VS2022中),在 不放大缩小控制台、 不F11开启全屏,这个 减小误差的代码在 44高156长的数组(也就是 全屏满字符),也 不用担心鼠标与实际坐标中误差带来的影响。
界面操作与游戏执行
在 game.h头文件中:
#pragma once
#include <stdio.h>
#include <Windows.h>
#define HIGH 5 // 设置游戏画面高度
#define WIGHT 5 // 设置游戏画面宽度
void gotoxy(int a, int b);
void game();
void getMousePlace(int* x, int* y);
在 test.c源文件中:
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
//声明POINT结构体类存储鼠标坐标
POINT* ptrpos;
//声明LPRECT结构体指针存储窗口坐标信息
LPRECT rect;
HWND hwnd;
void gotoxy(int a, int b) // 固定画面位置
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos;
pos.X = a;
pos.Y = b;
SetConsoleCursorPosition(handle, pos);
}
void HideCursor() // 隐藏光标
{
CONSOLE_CURSOR_INFO cursor_info = { 1, 0 };
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}
void getMousePlace(int* x, int* y)
{
//根据上述设定的标题查找句柄,第一个形参设为NULL意味着查找系统中所有句柄
hwnd = FindWindow(NULL, "憨憨");
//根据查找标题"憨憨"得到的句柄返回对于窗口的坐标并存储在rect中
GetWindowRect(hwnd, rect);
//返回鼠标对应的坐标并存储在ptrpos中
GetCursorPos(ptrpos);
*x = ptrpos->x - rect->left;//鼠标横坐标-控制台窗口左端横坐标得到鼠标相对控制台窗口的横坐标
*y = ptrpos->y - rect->top;//同理得到鼠标相对的纵坐标
//鼠标坐标方块化
// 减小x坐标误差
int fixX = (*x - 14) / 10;
*x = (int)(*x + fixX * 0.4);
// 方块化x坐标
*x = (*x - 14) / 10;
// 减小y坐标误差
int fixY = (*y - 51) / 18;
*y = (int)(*y - fixY * 0.9);
// 方块化y坐标
*y = (*y - 51) / 18;
}
void menu()
{
int y = 0;
int x = 0;
while (1)
{
gotoxy(0, 0); // 固定画面
getMousePlace(&x, &y);
char arr[2][13] = { "开始游戏", "退出游戏" };
printf("********************\n");
// 对应鼠标坐标与数组
if ((y == 1) && (4 <= x && x <= 15))
{
printf("****\033[41m%12s\033[0m****\n", &arr[0][0]);
if (GetAsyncKeyState(VK_LBUTTON))
{
system("cls");
game();
continue;
}
}
else
printf("****%-12s****\n", &arr[0][0]);
if ((y == 2) && (4 <= x && x <= 15))
{
printf("****\033[41m%12s\033[0m****\n", &arr[1][0]);
if (GetAsyncKeyState(VK_LBUTTON))
{
system("cls");
printf("退出游戏\n");
break;
}
}
else
printf("****%-12s****\n", &arr[1][0]);
printf("********************\n");
// 截取无用的鼠标信息
GetAsyncKeyState(VK_RBUTTON);
GetAsyncKeyState(VK_LBUTTON);
Sleep(50);
}
}
int main()
{
//为结构体指针分配内存空间
ptrpos = (POINT*)malloc(sizeof(POINT) + 64);
rect = (LPRECT*)malloc(sizeof(LPRECT) + 64);
SetConsoleTitle("憨憨");//设定窗口标题
HideCursor();// 隐藏光标
menu();// 菜单选择
free(ptrpos);
free(rect);
return 0;
}
在 game.c源文件中:
#include "game.h"
extern POINT* ptrpos;
extern LPRECT rect;
extern HWND hwnd;
void init(char arr[HIGH][WIGHT])
{
for (int i = 0; i < HIGH; i++)
for (int j = 0; j < WIGHT; j++)
arr[i][j] = '*';
}
void game()
{
// 手动对上次界面鼠标信息进行截停
for (int i = 0; i < 10; i++)
{
GetAsyncKeyState(VK_RBUTTON);
GetAsyncKeyState(VK_LBUTTON);
Sleep(10);
}
char arr[HIGH][WIGHT] = { 0 };
init(arr);
int y = 0;
int x = 0;
while (1)
{
gotoxy(0, 0);
getMousePlace(&x, &y);
for (int i = 0; i < HIGH; i++)
{
for (int j = 0; j < WIGHT; j++)
{
if (i == y && j * 2 == x)
{
printf("\033[42m%c\033[0m ", arr[i][j]); // 背景色绿色
if (GetAsyncKeyState(VK_LBUTTON) && arr[i][j] == '*') // 左键
{
if (arr[i][j] == '*')
arr[i][j] = '#';
}
if (GetAsyncKeyState(VK_RBUTTON) && arr[i][j] == '#') // 右键
{
if (arr[i][j] == '#')
arr[i][j] = '*';
}
}
else
{
printf("%c ", arr[i][j]);
}
}
printf("\n");
}
// 返回界面
printf("**************\n");
if ((y == 6) && (4 <= x && x <= 9))
{
printf("****\033[41m%6s\033[0m****\n", "返回");
if (GetAsyncKeyState(VK_LBUTTON)) // 左键
{
system("cls");
break;
}
}
else
{
printf("****%-6s****\n", "返回");
}
printf("**************\n");
// 截取无用的鼠标信息
GetAsyncKeyState(VK_RBUTTON);
GetAsyncKeyState(VK_LBUTTON);
Sleep(50);
}
}
程序演示:
鼠标程序展示5
总结
第一种鼠标操作相对正式,会自动索取鼠标坐标,也不需要对无用的鼠标信息进行处理,但是Bug除了以上两个,我也不确定还有没有其他的。
第二种鼠标操作优点也很明现,不需要获取鼠标操作,也不用担心scanf函数、getch函数等键盘输入字符导致程序异常,但是需要手动将鼠标方格化,也要对无用的鼠标信息进行手动处理。
实例:将C语言扫雷实现鼠标操作
(注:以下的代码和被修改的代码来自本人上两篇博客)
我这里的扫雷使用的是第一种鼠标操作,就直接放源代码了
在 game.h源文件中:
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <time.h> // time 函数需要的头文件
#include <stdlib.h> // rand、 srand、malloc 函数需要的头文件
#include <windows.h>
void initBoard(char** arr, int rows, int cols, char set);
int display(char** arr, int row, int col, int y, int x, int* time);
void setMine(char** mine, int row, int col, int y, int x);
void findMine(char** mine, char** show, int row, int col);
void spread(char** mine, char** show, int y, int x, int* win, int row, int col);
void numberSpread(char** mine, char** show, int y, int x, int num, int* win, int row, int col);
// 动态扫雷实现
char** apply(int* rows, int* cols);
void release(char** arr, int rows, int cols);
int getMine();
// 伪图形扫雷实现
void game();
void gotoxy(int a, int b);
void HideCursor();
int option(int* rows, int* cols);
// 鼠标操作
void getPower();
void mouseOperateGame(char** mine, char** show, int mouseOperate, int y, int x, int row, int col, int* first, int* win);
在 test.c源文件中:
#include "game.h"
int getMineCount = 0;
//定义句柄变量
HANDLE get1;
//权限准备
DWORD get2 = ENABLE_EXTENDED_FLAGS | ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT;
//定义输入事件结构体
INPUT_RECORD mouseRecord;
//用于存储读取记录
DWORD res;
void getPower() // 获取权限
{
if (!SetConsoleMode(get1, get2))
fprintf(stderr, "%s\n", "SetConsoleMode");
}
void adjustScreen()
{
system("cls");
printf("Ctrl + 滑动鼠标滑轮调整大小\n");
printf("注意:若实际游戏画面大于程序窗口会出现刷屏哦\n");
printf("按任意键返回:>\n");
char ch = _getch();
system("cls");
}
void menu1()
{
getPower();
char a = 0;
int y = 0;
int x = 0;
int exitgame = 0;
int judge = 1;
while (1)
{
gotoxy(0, 0);
//读取输入事件
ReadConsoleInput(get1, &mouseRecord, 1, &res);
//获取鼠标当前位置
y = mouseRecord.Event.MouseEvent.dwMousePosition.Y;
x = mouseRecord.Event.MouseEvent.dwMousePosition.X;
char arr[3][13] = { "开始游戏", "调整画面", "退出游戏" };
printf("*************************\n");
if ((y == 1) && (4 <= x && x <= 21))
{
printf("****\033[41m %-10s \033[0m****\n", &arr[0][0]);
}
else
printf("**** %-12s ****\n", &arr[0][0]);
if ((y == 2) && (4 <= x && x <= 21))
{
printf("****\033[41m %-10s \033[0m****\n", &arr[1][0]);
}
else
printf("**** %-12s ****\n", &arr[1][0]);
if ((y == 3) && (4 <= x && x <= 21))
{
printf("****\033[41m %-10s \033[0m****\n", &arr[2][0]);
}
else
printf("**** %-12s ****\n", &arr[2][0]);
printf("*************************\n");
printf("若点一下无反应可重新点一下\n");
int mouseOperate = 0;
// 利用GetAsyncKeyState和judge变量可以在getch()得到字符后
// 对鼠标异常进行标记,使下一次GetAsyncKeyState
// 不会对异常信息进行处理,这时只需再点击左键,
// 代码会获取新的信息,避免异常信息停留。
if (judge)
mouseOperate = mouseRecord.Event.MouseEvent.dwButtonState;
else if (GetAsyncKeyState(VK_LBUTTON))
judge = 1;
else
mouseOperate = 0;
if (mouseRecord.EventType == MOUSE_EVENT)
{
switch (mouseOperate)
{
case FROM_LEFT_1ST_BUTTON_PRESSED:
if ((y == 1) && (4 <= x && x <= 21))
{
system("cls"); // 先清屏
game();
getPower();
}
if ((y == 2) && (4 <= x && x <= 21))
{
adjustScreen();
judge = 0;
getPower();
}
if ((y == 3) && (4 <= x && x <= 21))
{
system("cls");// 先清屏
printf("%s\n", "退出游戏");
exitgame = 1;
}
break;
}
}
// 利用GetAsyncKeyState和judge变量可以在getch()得到字符后
// 对鼠标异常进行标记,使下一次GetAsyncKeyState
// 不会对异常信息进行处理,这时只需再点击左键,
// 代码会获取新的信息,避免异常信息停留。
GetAsyncKeyState(VK_LBUTTON);
Sleep(50);
if (exitgame)
break;
}
}
int option(int* rows, int* cols)
{
GetAsyncKeyState(VK_LBUTTON);
getPower();
char a = 0;
int y = 0;
int x = 0;
while (1)
{
gotoxy(0, 0); // 固定画面
//读取输入事件
ReadConsoleInput(get1, &mouseRecord, 1, &res);
//获取鼠标当前位置
y = mouseRecord.Event.MouseEvent.dwMousePosition.Y;
x = mouseRecord.Event.MouseEvent.dwMousePosition.X;
char arr[4][20] = { "1.初级(9×9)", "2.中级(16×16)", "3.高级(16×30)", "返回" };
printf("*************************\n");
if ((y == 1) && (4 <= x && x <= 22))
printf("****\033[41m %-15s\033[0m****\n", &arr[0][0]);
else
printf("**** %-15s****\n", &arr[0][0]);
if ((y == 2) && (4 <= x && x <= 22))
printf("****\033[41m %-15s\033[0m****\n", &arr[1][0]);
else
printf("**** %-15s****\n", &arr[1][0]);
if ((y == 3) && (4 <= x && x <= 22))
printf("****\033[41m %-15s\033[0m****\n", &arr[2][0]);
else
printf("**** %-15s****\n", &arr[2][0]);
if ((y == 4) && (4 <= x && x <= 22))
printf("****\033[41m %-14s\033[0m****\n", &arr[3][0]);
else
printf("**** %-14s****\n", &arr[3][0]);
printf("*************************\n");
int mouseOperate = mouseRecord.Event.MouseEvent.dwButtonState;
if (mouseRecord.EventType == MOUSE_EVENT)
{
switch (mouseOperate)
{
case FROM_LEFT_1ST_BUTTON_PRESSED:
if ((y == 1) && (4 <= x && x <= 22))
{
*rows = 11;
*cols = 11;
return 1;
}
if ((y == 2) && (4 <= x && x <= 22))
{
*rows = 18;
*cols = 18;
return 2;
}
if ((y == 3) && (4 <= x && x <= 22))
{
*rows = 18;
*cols = 32;
return 3;
}
if ((y == 4) && (4 <= x && x <= 22))
{
return -1;
}
break;
}
}
Sleep(50);
}
return 1;
}
void game()
{
int rows = 0;
int cols = 0;
int num = option(&rows, &cols);
system("cls");
if (num < 0)
return;
char** mine = apply(&rows, &cols);
char** show = apply(&rows, &cols);
getMineCount = 0;
getMineCount = getMine(num);
int row = rows - 2;
int col = cols - 2;
initBoard(mine, rows, cols, '0');
initBoard(show, rows, cols, '*');
findMine(mine, show, row, col);
release(mine, rows, cols);
release(show, rows, cols);
}
void test()
{
get1 = GetStdHandle(STD_INPUT_HANDLE);
HideCursor();
srand((unsigned int)time(NULL)); // 使rand函数产生伪随机数
menu1();
CloseHandle(get1);
}
int main()
{
test();
return 0;
}
在 game.c源文件中:
#include "game.h"
extern int getMineCount;
extern HANDLE get1;
extern DWORD get2;
extern INPUT_RECORD mouseRecord;
extern DWORD res;
void gotoxy(int a, int b)
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos;
pos.X = a;
pos.Y = b;
SetConsoleCursorPosition(handle, pos);
}
void HideCursor()
{
CONSOLE_CURSOR_INFO cursor_info = { 1, 0 };
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}
void initBoard(char** arr, int rows, int cols, char set)
{
for (int i = 0; i < rows; i++)
for (int j = 0; j < cols; j++)
arr[i][j] = set;
}
int display(char** arr, char** mine, char** show, int row, int col, int y, int x, int time1, int* first, int* win, int mouseOperate)
{
int FCount = 0;
// 将游戏时间生命周期延长,防止游戏完成打印为0
static int time2 = 0;
if (win != NULL)
{
//游戏进行时间 为 当前时间 减去 进入游戏的时间
time2 = (int)time(NULL) - time1;
// 遗憾的是我不知道第一种鼠标操作有没有
// 同khbit()一样有非阻塞函数对鼠标进行监听
}
int i, j;
for (i = 0; i <= col / 2 - 2; i++)
printf("--");
printf("time:%-3d", time2);
for (i = 0; i <= col / 2 - 3; i++)
printf("--");
printf("\n");
for (i = 1; i <= row; i++)
for (j = 1; j <= col; j++)
if (arr[i][j] == 'F')
FCount++;
if (FCount > getMineCount) // 防止雷数量成为负数
FCount = getMineCount;
for (i = 1; i <= row; i++)
{
printf("|");
for (j = 1; j <= col; j++) // 打印扫雷内容
{
if (i == y && j * 2 == x + 1)
{
printf("\033[42m%c\033[0m ", arr[i][j]);
if (mouseRecord.EventType == MOUSE_EVENT)
{
mouseOperateGame(mine, show, mouseOperate, i, j, row, col, first, win, FCount);
}
}
else
{
if (arr[i][j] == '*')
printf("%c ", arr[i][j]);
if (arr[i][j] == '0')
printf("\033[30m%c\033[0m ", arr[i][j]); // 黑色
if (arr[i][j] == '1')
printf("\033[36m%c\033[0m ", arr[i][j]); // 浅蓝色
if (arr[i][j] == '2')
printf("\033[34m%c\033[0m ", arr[i][j]); // 蓝色
if (arr[i][j] == '3')
printf("\033[33m%c\033[0m ", arr[i][j]); // 黄色
if (arr[i][j] == '4')
printf("\033[31m%c\033[0m ", arr[i][j]); // 红色
if (arr[i][j] == '5')
printf("\033[35m%c\033[0m ", arr[i][j]); // 紫色
if (arr[i][j] == '6')
printf("%c ", arr[i][j]);
if (arr[i][j] == '7')
printf("%c ", arr[i][j]);
if (arr[i][j] == 'F')
printf("\033[32m%c\033[0m ", arr[i][j]); // 绿色
}
}
printf("\n"); // 换行
}
for (i = 0; i <= col / 2 - 2; i++)
printf("--");
printf("雷:\033[31m%d\033[0m", getMineCount - FCount); // 表示当前雷的数量
for (i = 0; i <= col / 2 - 2; i++)
printf("--");
printf("\n");
}
void setMine(char** mine, int row, int col, int y, int x)
{
int count = 0;
int i, j;
// 记录输入行和旁边的两行
int judgeRow[3] = { 0 };
for (int k = -1, a = 0; k <= 1; k++, a++)
judgeRow[a] = k + y;
// 记录输入列和旁边的两列
int judgeCol[3] = { 0 };
for (int k = -1, a = 0; k <= 1; k++, a++)
judgeCol[a] = k + x;
while (count < getMineCount) // 布置雷的数量,数量到达时则跳出循环
{
i = rand() % row + 1; // 行
// 当输入行且旁边两行与布置行不同时
if (i != judgeRow[0] && i != judgeRow[1] && i != judgeRow[2])
j = rand() % col + 1; // 列
// 当输入行或旁边两行与布置行相同时
else
{
do
{
j = rand() % col + 1;
// 若输入列或旁边两列与布置列相同则进入循环
} while (j == judgeCol[0] || j == judgeCol[1] || j == judgeCol[2]);
}
if (mine[i][j] == '0') // 若为 '0',则放置雷
{
mine[i][j] = '1';
count++;
}
}
}
int mineCount(char** mine, int y, int x)
{
int count = 0;
for (int i = -1; i <= 1; i++)
for (int j = -1; j <= 1; j++)
if (mine[y + i][x + j] == '1')
count++;
return count;
}
void mouseOperateGame(char** mine, char** show, int mouseOperate, int y, int x, int row, int col, int* first, int* win, int FCount)
{
switch (mouseOperate)
{
case FROM_LEFT_1ST_BUTTON_PRESSED:
if (show[y][x] == '*') // 判断输入的坐标是否被占用
{
if (*first)
{
setMine(mine, row, col, y, x);
*first = 0;
}
if (mine[y][x] == '1') // 判断输入的坐标是否是雷
{
*win = -9;
}
else
{
int count = mineCount(mine, y, x);
show[y][x] = count + '0';
(*win)++;
spread(mine, show, y, x, win, row, col);
}
}
if (show[y][x] == 'F')
show[y][x] = '*';
if (show[y][x] >= '1' && show[y][x] <= '7')
numberSpread(mine, show, y, x, show[y][x] - '0', win, row, col);
break;
case RIGHTMOST_BUTTON_PRESSED:
if (show[y][x] == '*' && FCount < getMineCount)
show[y][x] = 'F';
break;
}
}
void findMine(char** mine, char** show, int row, int col)
{
getPower();
// 注:由于扫雷下标从1开始,在 3.游戏操作 中需要变通一下
int x = 1; // 横轴移动
int y = 1; // 纵轴移动
int time1 = (int)time(NULL); // 获取进入游戏的时间
int win = 0;
int mouseOperate = 0;
int first = 1;
int falseTime = 1;
int winTime = 1;
while (1)
{
gotoxy(0, 0);
ReadConsoleInput(get1, &mouseRecord, 1, &res);
//获取鼠标当前位置
y = mouseRecord.Event.MouseEvent.dwMousePosition.Y;
x = mouseRecord.Event.MouseEvent.dwMousePosition.X;
mouseOperate = mouseRecord.Event.MouseEvent.dwButtonState;
// 当win为负数意思为被雷炸死
if (win < row * col - getMineCount && win >= 0)
{
display(show, mine, show, row, col, y, x, time1, &first, &win, mouseOperate);
if ((y == row + 2) && (3 <= x && x <= 14))
{
printf("***\033[41m %6s \033[0m***\n", "返回");
if (mouseOperate == FROM_LEFT_1ST_BUTTON_PRESSED)
{
system("cls");
break;
}
}
else
printf("*** %6s ***\n", "返回");
}
if (win >= row * col - getMineCount)
{
if (winTime)
{
system("cls");
getPower();
winTime = 0;
}
display(show, mine, show, row, col, y, x, time1, NULL, NULL, 0);
printf("**********************\n");
printf("****** 你赢了 ******\n");
printf("**********************\n");
if ((y == row + 5) && (5 <= x && x <= 16))
{
printf("*****\033[41m %6s \033[0m*****\n", "返回");
if (mouseOperate == FROM_LEFT_1ST_BUTTON_PRESSED)
{
system("cls");
break;
}
}
else
printf("***** %6s *****\n", "返回");
}
if (win < 0)
{
if (falseTime)
{
system("cls");
getPower();
falseTime = 0;
}
display(mine, mine, show, row, col, y, x, time1, NULL, NULL, 0);
printf("**********************\n");
printf("**很遗憾,你被炸死了**\n");
printf("**********************\n");
if ((y == row + 5) && (5 <= x && x <= 16))
{
printf("*****\033[41m %6s \033[0m*****\n", "返回");
if (mouseOperate == FROM_LEFT_1ST_BUTTON_PRESSED)
{
system("cls");
break;
}
}
else
printf("***** %6s *****\n", "返回");
}
Sleep(100);
}
}
void spread(char** mine, char** show, int y, int x, int* win, int row, int col)
{
if (show[y][x] == '0') // 若周围没有雷才进入
{
for (int i = -1; i <= 1; i++) // 行
{
for (int j = -1; j <= 1; j++) // 列
{
if (y + i >= 1 && y + i <= row && x + j >= 1 && x + j <= col) // 防止超出9×9的范围
{
if (show[y + i][x + j] == '*') // 防止反复递归同一个坐标
{
int count = mineCount(mine, y + i, x + j);
show[y + i][x + j] = count + '0'; // 将已经递归过的坐标显示它周围雷的数量,防止反复递归同一个坐标
(*win)++; // 增加查找非雷坐标的数量
spread(mine, show, y + i, x + j, win, row, col); // 进入下一次递归
}
}
}
}
}
}
void numberSpread(char** mine, char** show, int y, int x, int num, int* win, int row, int col)
{
int unknown = 0; // 未知坐标的数量
int unknownBlank = 0;// 未知坐标且未插旗的数量
int FCount = 0; // 插旗坐标的数量
// 记录周围三者的数量
for (int i = -1; i <= 1; i++)
{
for (int j = -1; j <= 1; j++)
{
if (show[y + i][x + j] == '*' || show[y + i][x + j] == 'F')
unknown++;
if (show[y + i][x + j] == 'F')
FCount++;
if (show[y + i][x + j] == '*')
unknownBlank++;
}
}
if (unknown > num && FCount >= num && unknownBlank != 0) // 周围未知的坐标的数量要大于周围雷的数量
{
for (int i = -1; i <= 1; i++) // 行
{
for (int j = -1; j <= 1; j++) // 列
{
// 防止进入边框坐标
if (y + i >= 1 && y + i <= row && x + j >= 1 && x + j <= col)
{
// 数字展开时扫到雷
if (show[y + i][x + j] == '*' && mine[y + i][x + j] == '1')
{
(*win) = -9;
}
// 数字展开扫到非雷
else if (show[y + i][x + j] == '*' && mine[y + i][x + j] == '0')
{
int count = mineCount(mine, y + i, x + j);
show[y + i][x + j] = count + '0';
(*win)++;
spread(mine, show, y + i, x + j, win, row, col);
}
}
}
}
}
}
char** apply(int* rows, int* cols)
{
// 申请二级指针(二维数组)行的数量
char** arr = (char**)malloc(sizeof(char*) * (*rows));
if (NULL == arr)
{
printf("游戏异常,已退出\n");
exit(-1);
}
// 申请每行之中列的数量
for (int i = 0; i < *rows; i++)
{
arr[i] = (char*)malloc(sizeof(char) * (*cols));
if (NULL == arr[i])
{
printf("游戏异常,已退出\n");
exit(-1);
}
}
// 返回首元素地址
return arr;
}
void release(char** arr, int rows, int cols)
{
// 先释放一级指针空间
for (int i = 0; i < rows; i++)
free(arr[i]);
// 后释放二级指针的空间
free(arr);
}
int getMine(int num)
{
switch (num)
{
case 1:
return 10;
case 2:
return 40;
case 3:
return 99;
}
return 0;
}
// 这里删除自定义操作,一是怕出现Bug,二是懒得去改。
演示视频我放在开头。
结语:
以上是我对C语言两种鼠标操作的理解和经验总结,如果游戏有Bug或文章有错误,还请指出, 我会去更改。
谢谢阅读