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或文章有错误,还请指出, 我会去更改。

谢谢阅读

  • 17
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值