精华内容
下载资源
问答
  • 扫雷游戏算法- 扫雷是什么鬼?扫雷里包含什么算法?.mp4
  • c语言实现的扫雷游戏

    2021-01-30 21:26:25
    文章目录扫雷游戏前言一、扫雷是什么?二、扫雷步骤1.引入头文件2.game.c文件1.menu函数打印游戏菜单2.InitBoard初始化数组3.DisplayBoard函数在屏幕上打印数组4.SetMine函数布置雷5.FindMine函数排雷3.引入main.c主...

    扫雷游戏

    扫雷游戏根据的是c语言中二维数组和循环结构和加上函数的使用就可以实现简单的扫雷。



    前言

    一、扫雷是什么?

    扫雷是一个二维数组的使用,加上多种函数的定义和实现,循环结构控制游戏进行的次数

    二、扫雷步骤

    1.引入头文件

    用define对雷个数和数组行、列定义, 在实现对函数的定义。

    代码如下(示例):

    #include <stdio.h>
    #include<stdlib.h>
    #include<math.h>
    
    #define Easy_Count 10
    #define ROW 9
    #define COL 9
    #define ROWS ROW+2
    #define COLS COL+2
    
    void menu();//打印菜单
    void IninBoard(char Board[ROWS][COLS],int rows,int cols,char ret);//rows和cols 是形参名,不与上面define定义的符号一样
    void DisplayBoard(char Board[ROWS][COLS], int row, int col);//打印数组
    void SetMine(char mine[ROWS][COLS], int row, int col,int count);//布置雷count
    void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);//排雷
    

    2.game.c文件

    这里主要对二维数组中各个函数的声明,实现各个函数的功能。

    1.menu函数打印游戏菜单

    如图:
    在这里插入图片描述图片

    void menu()
    {
    	printf("***************************\n");
    	printf("**********1.play***********\n");
    	printf("**********0.exit***********\n");
    	printf("***************************\n");
    }
    

    2.InitBoard初始化数组

    InitBoard函数实现对二维数组的初始化,数对数组mine和show分别初始化为不同的符号,用ret参数来接收。

    void InitBoard(char Board[ROWS][COLS], int rows, int cols,char ret)
    {
    	int i = 0;
    	int j = 0;
    	for (i = 0; i < rows; i++)
    	{
    		for (j = 0; j < cols; j++)
    		{
    			Board[i][j] = ret;
    		}
    	}
    }
    

    3.DisplayBoard函数在屏幕上打印数组

    如图用循环来控制数组的行和列的打印!
    在这里插入图片描述

    void DisplayBoard(char Board[ROWS][COLS], int row, int col)
    {
    	int i = 0;
    	int j = 0;
    	printf("------------扫雷游戏------------\n");
    	for (i = 0; i <=row; i++)
    	{
    		printf("%d ", i);//打印行坐标0-9
    	}
    	printf("\n");
    	for (i = 1; i <= row; i++)
    	{
    		printf("%d ", i);//打印列坐标1-9
    		for (j = 1; j <= col; j++)
    		{
    			printf("%c ", Board[i][j]);
    		}
    		printf("\n");
    	}
    	printf("------------扫雷游戏------------\n");
    }
    

    4.SetMine函数布置雷

    在二维数组中随机布置雷。

    void SetMine(char mine[ROWS][COLS], int row, int col,int count)//布置雷
    {
    	while (count)
    	{
    		int x = rand() % row + 1;
    		int y = rand() % col + 1;
    		if (mine[x][y] = '0')
    		{
    			mine[x][y] = '1';
    			count--;//布置成功则雷少1,cout--
    		}
    	}
    }
    

    5.FindMine函数排雷

    在二维数组中输入合法的坐标来找雷!

    void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
    {
    	int x = 0;
    	int y = 0;
    	int win = 0;
    	while (win<row*col-Easy_Count)
    	{
    		printf("请输入要排雷的坐标:");
    		scanf("%d%d", &x, &y);
    		if (x >= 1 && x <= row&&y >= 1 && y <= col)//判断坐标合法性
    		{
    			if (mine[x][y] == '1')
    			{
    				printf("你被炸死了!\n");
    				DisplayBoard(mine, row, col);
    				break;
    			}
    			else//不是雷,则统计雷的个数
    			{
    				int count = GetMine(mine, x, y);//以九宫格中心为起点,排查周围8个坐标有没有雷
    				show[x][y] = count + '0';
    				DisplayBoard(show, row, col);
    				win++;
    			}
    		}
    		else
    		{
    			printf("输入的坐标非法,请重新输入!\n");
    		}
    	}
    	if (win == Easy_Count)
    	{
    		printf("恭喜你,排雷成功!\n");
    	}
    }
    

    用GetMine函数在九宫格为中心排查周围8个坐标雷的个数!

    int GetMine(char mine[ROWS][COLS], int x, int y)//统计mine数组9宫格中雷的个数
    {
    	return mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1] + mine[x][y - 1] + mine[x][y + 1] + mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] - 8 * '0';
    }
    

    3.引入main.c主文件

    1.do-while循环

    用do-while循环来控制游戏进行的次数!

    int main()
    {
    	srand((unsigned int)time(NULL));//用时间戳来生成随机的坐标
    	int input = 0;
    	do{
    		menu();
    		printf("请输入你的选择:");
    		scanf("%d", &input);
    		switch (input)
    		{
    		case 1:
    			game();
    			break;
    		case 0:
    			printf("退出游戏!\n");
    			break;
    		default:
    			printf("你输入的有错误,请重新输入!\n");
    			break;
    		}
    
    	} while (input);
    	return 0;
    }
    

    2.game函数的实现

    用game函数实现对各个函数的使用!

    void game()
    {
    	char mine[ROWS][COLS] = { 0 }; //不打印在屏幕的数组mine11行11列
    	char show[ROWS][COLS] = { 0 };//打印在屏幕上的数组show9行9列
    	InitBoard(mine, ROWS,COLS,'0');//初始化数组置为符号0
    	InitBoard(show, ROWS, COLS,'*');//初始化数组置为符号*
    	//DisplayBoard(mine, ROW,COL);//打印数组显示在屏幕上9行9列
    	DisplayBoard(show, ROW,COL);//打印数组显示在屏幕上9行9列
    	SetMine(mine, ROW, COL,Easy_Count);
    	//DisplayBoard(mine, ROW, COL);//
    	FindMine(mine,show, ROW, COL);
    }
    

    三、总结

    以上就是今天要完成的内容,一个简易的扫雷程序,本文仅仅简单介绍了二维数组的使用和各个函数的的使用,如果上述有任何问题,请懂哥指教,不过没关系,主要是自己能坚持,更希望有一起学习的同学可以帮我指正,但是如果可以请温柔一点跟我讲,爱与和平是永远的主题,爱各位了。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210130212447460.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0OTE4MDkw,size_16,color_FFFFFF,t_70)
    展开全文
  • 扫雷

    2019-04-09 16:55:41
    首先来分析一下扫雷游戏的过程,先要显示菜单,...核心代码计算并显示周围雷的个数,通过这次学习,我懂得了,在写每一个程序之前应该明白每一步该做什么,每一个函数应该实现什么样的功能。 #include"mine.h" int...

    首先来分析一下扫雷游戏的过程,先要显示菜单,玩家选择是否玩游戏,然后需要两个数组一个布雷,另一个显示,游戏中需要显示周边雷的个数,首先说布雷,用rand()函数获得随机数,也就是随机位置,把雷布在这些地方,玩家输入位置玩游戏,核心代码是计算并显示周围雷的个数,通过这次学习,我懂得了,在写每一个程序之前应该明白每一步该做什么,每一个函数应该实现什么样的功能。

    #include"mine.h"
    int main()
    {
    	int select = 0;
    	int quit = 0;
    	while (!quit)
    	{
    		Menu();
    		scanf("%d", &select);
    		switch (select)
    		{
    		case 1:
    			Game();
    			break;
    		case 2:
    			printf("Bye Bye!\n");
    			quit = 1;
    			break;
    		default:
    			printf("Please Enter  Again!\n ");
    			break;
    		}
    	}
    	system("pause");
    	return 0;
    }
    

    函数的声明

    #ifndef _MINE_H_
    #define _MINE_H_
    #define _CRT_SECURE_NO_WARNINGS 1
    #include<stdio.h>
    #include<windows.h>
    #include<time.h>
    #include<string.h>
    #pragma warning(disable:4996)
    #define ROW 12
    #define COL 12
    #define MINES 20
    void Menu();
    void Game();
    void SetMine(char mine_board[][COL], int row, int col);
    void PlayGame(char show_board[][COL], char mine_board[][COL], int row, int col);
    int GetMineNum(char mine_board[][COL], int i, int j);
    void ShowBoard(char show_board[][COL], int row, int col);
    #endif
    

    函数的实现

    #define _CRT_SECURE_NO_WARNINGS 1
    #include"mine.h"
    void Menu()//显示菜单,玩家选择是否玩游戏
    {
    	printf("****************************************\n");
    	printf("******   1 Play         2  Exit   ******\n");
    	printf("****************************************\n");
    	printf("Please Select:>");
    }
    static int GetIndex(int start, int end)
    {
    	return rand() % (end - start + 1) + start;//获得start到end之间的随机数
    }
    void SetMine(char mine_board[][COL], int row, int col)//布雷
    {
    	srand((unsigned long)time(NULL));//种下随机数的种子
    	int mine_num = MINES;//雷的个数为MINES
    	while (mine_num)
    	{
    		int i_index = GetIndex(1, col - 2);
    		int j_index = GetIndex(1, col - 2);//获得随机数得到位置
    		if (mine_board[i_index][j_index] == '0')
    		{
    			mine_board[i_index][j_index] == '1';//在(i,j)处布雷
    			mine_num--;//每布一次雷,雷的个数就少一个
    		}
    	}
    }
    int GetMineNum(char mine_board[][COL], int i, int j)//获得周边雷的个数
    {
    	return mine_board[i - 1][j - 1] + mine_board[i - 1][j] + mine_board[i - 1][j + 1] \
    		+ mine_board[i][j - 1] + mine_board[i][j + 1] + mine_board[i + 1][j - 1] + mine_board[i + 1][j] + mine_board[i + 1][j + 1] - 8 * '0';//有雷的地方为'1',没有雷的地方为'0',访问周边元素相加减去8个'0',就是雷的个数
    }
    void ShowBoard(char show_board[][COL], int row, int col)//显示游戏界面
    {
    	int i = 1;
    	int j = 1;
    	printf(" ");
    	for (; i < col - 2; i++)
    	{
    		printf("%d", i);
    	}
    	printf("\n");
    	for (i = 1; i <= col - 1; i++)
    	{
    		printf("----");
    	}
    	printf("\n");
    	for (i = 1; i <= row - 2; j++)
    	{
    		printf("%2d|", i);
    
    		{for (j = 1; j <= col - 2; j++)
    		{
    			printf("%c|", show_board[i][j]);
    		}
    		printf("\n");
    		int k = 1;
    		for (; k <= col - 1; k++)
    		{
    			printf("----");
    		}
    		printf("\n");
    		}
    	}
    	void PlayGame(char show_board[][COL],char mine_board[][COL] ,int row, int col)
    	{
    		int i = 0;
    		int j = 0;
    		int total = (ROW - 2)*(COL - 2);
    		while (1)
    		{
    			ShowBoard(show_board, row, col);
    			printf("Pleae Enter Pos<x,y>:");
    			scanf("%d%d", &i, &j);
    			if (i >= 1 && i <= row - 2 && j >= 1 && j <= col - 2)
    			{
    				if (mine_board[i][j] == '0')
    				{
    					int num = GetMineNum(mine_board, i, j);
    					show_board[i][j] = num + '0';
    					total--;
    				}
    				else
    				{
    					ShowBoard(mine_board, row, col);
    					printf("Gmae Over!You Lose:(\n");
    					break;
    				}
    			}
    			else
    			{
    				printf("Enter Error,Try Again!\n");
    				continue;
    			}
    			if (total == MINES)
    			{
    				printf("You Win:)\n");
    				break;
    			}
    		}*/
    	}
    	void Game()
    	{
    		char show_board[ROW][COL];
    		char mine_board[ROW][COL];
    		memset(show_board, '*', sizeof(show_board));
    		memset(mine_board, '0', sizeof(mine_board));
    		SetMine(mine_board, ROW, COL);
    		PlayGame(show_board, mine_board, ROW, COL);
    	}
    
    
    展开全文
  • 第一篇文章先带领大家学习什么是逆向分析,然后详细讲解逆向分析的典型应用,接着通过OllyDbg工具逆向分析经典的游戏扫雷,再通过Cheat Engine工具复制内存地址获取,实现一个自动扫雷程序。基础性文章,西电UI您...

    从2019年7月开始,我来到了一个陌生的专业——网络空间安全。初入安全领域,是非常痛苦和难受的,要学的东西太多、涉及面太广,但好在自己通过分享100篇“网络安全自学”系列文章,艰难前行着。感恩这一年相识、相知、相趣的安全大佬和朋友们,如果写得不好或不足之处,还请大家海涵!

    接下来我将开启新的安全系列,叫“安全攻防进阶篇”,也是免费的100篇文章,作者将更加深入的去研究恶意样本分析、逆向分析、内网渗透、网络攻防实战等,也将通过在线笔记和实践操作的形式分享与博友们学习,希望能与您一起进步,加油~

    在这里插入图片描述

    第一篇文章先带领大家学习什么是逆向分析,然后详细讲解逆向分析的典型应用,接着通过OllyDbg工具逆向分析经典的游戏扫雷,再通过Cheat Engine工具复制内存地址获取,实现一个自动扫雷程序。该篇文章也是作者学习科锐钱林松老师在华中科技大学的分享视频,这里非常推荐大家去看看。话不多说,让我们开始新的征程吧!您的点赞、评论、收藏将是对我最大的支持,感恩安全路上一路前行,如果有写得不好或侵权的地方,可以联系我删除。基础性文章,希望对您有所帮助,作者的目的是与安全人共同进步,加油~

    作者的github资源:
    软件安全:https://github.com/eastmountyxz/Software-Security-Course
    其他工具:https://github.com/eastmountyxz/NetworkSecuritySelf-study
    Windows-Hacker:https://github.com/eastmountyxz/Windows-Hacker-Exp


    声明:本人坚决反对利用教学方法进行犯罪的行为,一切犯罪行为必将受到严惩,绿色网络需要我们共同维护,更推荐大家了解它们背后的原理,更好地进行防护。(参考文献见后)


    一.什么是逆向分析

    1.逆向工程

    科锐钱老师真的是大佬,拥有十余年的逆向工作经验,专注于先进技术的算法还原及逆向实训。作者从中学习到很多知识。本次课程分享的是逆向分析技术的引导,课程目标是带领大家了解下逆向分析是干什么的,在安全领域中是什么地位,并且穿插各种实战示例,尽量提高大家的兴趣。逆向分析是安全的基础行业,喜欢的人觉得很好玩,不喜欢的人觉得很苦逼。

    在讲逆向分析前,大家思考下:你有没有把别人的产品或Demo还原出源代码来过呢?而且就是作者的源代码,包括里面的BUG。

    • 反汇编:一次编译技术,阅读汇编代码反推出对应的高级代码,比如VC、GCC、Delphi等。
    • 反编译:通常在C#、Java、.NET框架等,因为它可以直接把元数据还原成高级代码,反编译其实更难,但是对使用的人更简单,比如Android的APK反编译成JAVA源代码。

    下面开始吧!第一个大家需要知道的是“什么是逆向工程?”


    什么是逆向工程?
    简单而言,一切从产品中提取原理及设计信息并应用于再造及改进的行为,都是逆向工程。在信息安全中,更多的是调查取证、恶意软件分析等,不管你用什么工具或手段,能达到其目的就算逆向分析。下图是《变形金刚》里面对它的描述,2007年的时候国内对信息安全重视程度也不高,对逆向分析也没有什么概念,真正重视是从老大提出“没有网络安全就没有国家安全”之后。而那时候的国外电影就已经用到了“reverse engineered”,说明国外导员对这个技术及应用场景都是了解的。

    在这里插入图片描述

    逆向工程最早是在二Z时的船舶工业,分析船的弱点,通过外形反推内部结构,其中粉红色是Amuno、黄色是引擎室、蓝色是龙骨、绿色是推进器等等。只有知道怎么造一个船后,才能进行逆向分析。

    在这里插入图片描述

    当然还有模具逆向、材料逆向、软件逆向,在我们的软件行业,就称为软件逆向。同样,在网络攻防中,你不可能先给出源码再进行攻击,通常在安全对抗中第一步要做的就是逆向分析,不管你用什么方式进行逆向分析,你都需要搞清楚:

    • 它是什么:样本是什么,良性的还是恶意的
    • 它干了什么:样本做了哪些事情
    • 我们怎么办:知道做了什么才能进行反制,如删除注册表启动项、清理感染的勒索病毒等

    在这里插入图片描述



    2.逆向分析的典型应用

    软件逆向有很多实现办法达到我们的目标,典型的应用包括软件工程、网络安全、司法取证、商业保护等。

    在这里插入图片描述


    逆向应用——病毒分析
    对于逆向分析,最大的行当就是病毒分析。对于一个安全企业来说,比如360,它的病毒分析团队属于它的主业,包括360、金山毒霸、腾讯医生等,它们主要业务就是根据一些恶意样本的行为,给出解决方案(专业查杀、完善引擎、应急响应),比如WannaCry爆发时,立刻分析其原因和传播漏洞,分析其影响程度及给出解决方案。所以,研究逆向病毒的人很多,当然坏的行当做游戏WG也很多,它们的对抗也是没有源码的,游戏安全人员会分析WG样本进行完善及修补。

    2000年早期制作病毒的人都比较单纯,写病毒是为了技术炫耀或开玩笑,典型的比如乒乓球病毒,每个周末都爆发,开启计算机后就有个乒乓球在电脑上弹,导致电脑不能用,而周一到周五恢复正常(可能是讨厌加班),此时的病毒没有获取用户隐私、删除数据等行为。

    在这里插入图片描述


    逆向利用——游戏保护
    从2005年开始,随着网游普及和网络虚拟财产(游戏装备)出现,大家也没有安全意识,出现了很多恶意程序和病毒,比如熊猫烧香,它是由李俊制作并肆虐网络的一款电脑病毒,是一款拥有自动传播、自动感染硬盘能力和强大的破坏能力的病毒,它不但能感染系统中exe、com、pif、src、html、asp等文件,它还能中止大量的反病毒软件进程并且会删除扩展名为gho的系统备份文件。

    同时,游戏WG也开始增多,并形成了包括私服、生产、PJ、DH等功能的生产线,通过分析游戏的关键数据结构,找到关键数据并对数据做修改以达到提升的效果。比如吃鸡游戏,如果你通过逆向分析找到每个玩家的坐标位置了,你是不是可以写一个透视G,前提是你知道其数据以什么形式存放在哪里,这就属于PJ挂。你甚至还可以修改攻击力、防御值,游戏的碰撞检测(两者间距离小于某个值)也可以取消实现飞天、穿墙等。

    在这里插入图片描述

    当然,我们的信息安全是正能量的,逆向分析主要是剖析病毒,包括:

    • 逆向病毒,获取病毒传播方法,可以遏制病毒传播
    • 逆向病毒,获取病毒隐藏手段,可以根除病毒
    • 逆向分析病毒,获取功能目的,可以溯源定位攻击者

    逆向应用——漏洞挖掘
    逆向应用还包括漏洞挖掘和漏洞利用,其中黑客挖掘漏洞的常用方法为:

    • 通过分析开源软件的源代码,获取漏洞
    • 通过分析产品本身获取漏洞
    • 通过分析可以利用漏洞的软件样本
    • 通过比较软件前后补丁的差异

    大家是否有研究过shellcode、缓冲区溢出?漏洞利用溢出缓冲区,首先要把关键内存、关键代码定位出来,这就属于逆向分析。在漏洞利用过程中,只有你越熟悉周围环境则可利用的漏洞就越多,比如逆向服务端,调用shell创建新用户功能,这个时候是没有源代码的,所以需要利用漏洞分析。

    逆向分析是信息安全行业的基础技术、必须技术和重要技术,只有你功力越深厚,则做的事情就越多。

    在这里插入图片描述


    漏洞利用——比较补丁
    下图展示了比较补丁前后差异的工具。比如官方软件在网上有安全更新,关注安全行情和漏洞公告的行当或企业会对比官方的补丁,在拿到官方升级后的软件,他们会对两个流程做比较,其中左边流程多了一个节点,说明升级就是这个位置,再详细分析为什么多了这个个检测。注意,官方公告通常会非常简略(补丁号、造成后果、影响范围),比如某个MP3播放器在播放某个冷门格式的音频文件时,会触发一个远程溢出问题,接下来我们就需要去做逆向分析,下载升级前和升级后的版本做流程对比。

    在这里插入图片描述


    逆向应用——电子取证
    通过样本追踪地理位置的实例,后续会给出一个实战案例。

    在这里插入图片描述


    漏洞利用——无文档学习
    表示没有源码的情况下获取程序信息,称为竞品分析。假设某个公司对同行的产品很感兴趣,想知道为什么它们的算法比我们的好,然后需要去分析和算法还原,这也是逆向分析的主要应用。最好的竞品分析,是能够将算法完美还原,两个代码再次编译后,除了地址不一样其他都一样(IDA查看)。注意,看懂代码完善程序并换另一种程序语言复现,算学习;而如果直接COPY别人的二进制或二进制序列,这算抄袭。

    在这里插入图片描述



    二.扫雷游戏逆向分析

    1.游戏介绍

    下面通过扫雷游戏进行逆向分析讲解。

    在这里插入图片描述

    此时我们打开一个工具,360会提示危险操作,点击“允许本次操作”即可。

    在这里插入图片描述

    此时就能够判断某个点是不是雷,从而方便我们点击完成扫雷游戏,O(∩_∩)O

    在这里插入图片描述

    接着进行逆向分析。扫雷中肯定有雷区的定义,作为程序员,你会怎么定义有雷或没有雷,或者插个旗子的状态呢?我们会使用一个二维数组来存储。那么,什么时候肯定会访问这个二维数组呢?在绘制整个游戏区、点击方格的时候都会访问。

    在绘制游戏区时,Windows编程有个关键函数,叫做——BeginPaint。BeginPaint函数为指定窗口进行绘图工作的准备,并用将和绘图有关的信息填充到一个PAINTSTRUCT结构中,所以它将是个突破口。



    2.OllyDbg动态分析

    接着我们使用Ollydbg打开,在逆向分析中,动态分析(OD)和静态分析(IDA)非常多,动静结合也是常用的分析手段。

    推荐作者上一个系列的两篇入门文章:

    • 静态分析:程序并未运行,通过分析文件的结构(格式)获取其内部原理。
    • 动态分析:在程序的运行过程中,分析其内部原理。
    • 灰盒分析:既不静态也不调试,通过一堆监控软件(注册表监控、文件监控、进程监控、敏感API监控)在虚拟机中跑程序,再分析恶意软件的大体行为,并形成病毒分析报告。

    至于哪种方法更好?我们需要具体问题具体分析,如果是分析扫雷游戏,因为没有危害可以动态调试分析,但如果是WannaCry蠕虫,你就不能在真机上动态分析。同时,很多安全公司为了及时响应各种安全事件,会把样本自动上传到服务器中,它们每天会收到成千上万的恶意样本,但可能存在某些未知样本只上传部分的原因,比如某个未知样本是个动态链接库,此时没有运行条件,只能进行静态分析或者模拟接口分析。

    在这里插入图片描述

    软件静态分析包括分析文件格式、分析网络协议、分析软件日志、修改存档文件等,下图展示了通过修改文件游戏作弊的示例。

    在这里插入图片描述

    软件动态调试可以用于HH翻译,比如《仙剑奇侠传》。

    在这里插入图片描述


    OllyDbg是一个新的动态追踪工具,将IDA与SoftICE结合起来的思想,Ring 3级调试器,非常容易上手,是当今最为流行的调试解密工具之一。它还支持插件扩展功能,是目前最强大的调试工具之一。OllyDbg打开如下图所示,包括反汇编窗口、寄存器窗口、信息窗口、数据窗口、堆栈窗口。

    • 反汇编窗口:显示被调试程序的反汇编代码,包括地址、HEX数据、反汇编、注释
    • 寄存器窗口:显示当前所选线程的CPU寄存器内容,点击标签可切换显示寄存器的方式
    • 信息窗口:显示反汇编窗口中选中的第一个命令的参数及跳转目标地址、字符等
    • 数据窗口:显示内存或文件的内容,右键菜单可切换显示方式
    • 堆栈窗口:显示当前线程的堆栈

    下图是打开EXE后显示的界面。

    OD常用的快捷键调试方式包括:

    F2
    设置断点,如下图所示的红色位置,程序运行到此处会暂停,再按一次F2键会删除断点。

    F9
    按下这个键运行程序,如果没有设置相应的点,被调试的程序直接开始运行。

    F8
    单步步过,每按一次这个按键,将执行反汇编窗口中的一条指令,遇到CALL等子程序不进入其代码。

    F7
    单步步入,功能通单步步过(F8)类似,区别是遇到CALL等子程序时会进入其中,进入后首先停留在子程序的第一条指令上。如下图进入CALL子程序。

    F4
    运行到选定位置,即运行到光标所在位置处暂停。

    CTRL+F9
    执行到返回,按下此键会执行到一个返回指令时暂停,常用于从系统领空返回到我们调试的程序领空。

    ALT+F9
    执行到用户代码,从系统领空快速返回我们调试的程序领空。


    接着正式分析扫雷程序。

    第一步:启动OllyDbg软件,选择菜单“文件”,打开winmine.exe文件。
    这里我们猜测游戏中存在一个二维数组,当我们显示界面时会访问这个二维数组,并且调用BeginPaint函数来显示界面。所以接下来需要找到调用BeginPain的位置。

    在这里插入图片描述


    第二步:在反汇编窗口右键鼠标,选择“查找”->“当前模块中的名称”。

    在这里插入图片描述

    当我们在键盘上输入“BEGINPAINT”时,能够迅速找到对应的函数。

    在这里插入图片描述


    第三步:点击右键选择“在每个参考上设置断点”。

    在这里插入图片描述

    接着点击下图所示的“B”进行断点设置界面。
    在这里插入图片描述

    双击该断点会进入到反汇编窗口BeginPaint对应位置。

    在这里插入图片描述


    第四步:按下“F9”运行程序,可以看到在BeginPaint和EndPaint之前有一个CALL函数。

    在这里插入图片描述

    选中该行右键“跟随”之后,去到0x01002AC3位置,发现又存在很多个CALL函数。

    在这里插入图片描述

    一种方法是一个一个函数分析,这里使用另一种方法。当我们在使用扫雷时,发现它的界面并没有闪烁,所以怀疑使用了 双缓存技术,这是我们的突破口。双缓存是在缓存中一次性绘制,再把绘制的结果返回在界面上。比如,你要在屏幕上绘制一个圆、正方形、直线,需要调用GDI的显示函数,操作显卡画一个圆,再画一个正方形和直线,它需要访问硬件三次;此时依赖硬件的访问速度,而且如果绘制错误擦除再绘制,需要反复的访问硬件,为了减少硬件操作,我们在内存中把需要绘制的图像准备好,然后一切妥当之后提交给硬件显示。

    PS:当然,为什么是双缓存技术呢?目前的我也无法理解。只有当我们做了大量的逆向分析后,才会养成一定的经验来帮助我们判断。任何行业和技术都是这样的,包括作者自己,目前也是一步一个脚印的去学习,去总结,去进步。期待与您前行,加油~


    第五步:继续在反汇编窗口右键鼠标,选择“查找”->“当前模块中的名称”,找到双缓存技术的核心函数BitBlt。
    BitBlt是将内存中的数据提交到显示器上,该函数对指定的源设备环境区域中的像素进行位块(bit_block)转换,以传送到目标设备环境。

    在这里插入图片描述

    点击右键选择“在每个参考上设置断点”,如下图所示,此时绘制了两个断点。

    在这里插入图片描述


    第六步:运行程序去到第二个断点BitBlt位置。

    在这里插入图片描述

    注意,此时显示了两层循环,刚好符合我们二维数组的遍历,按F8单步步过可以动态调试观察其效果。

    在这里插入图片描述


    第七步:在0x01002700位置按下F2取消断点,并在该函数的起始位置0x010026A7设置断点,接下来需要详细分析这个双缓存函数绘制过程。

    在这里插入图片描述

    代码中,ESI首先通过XOR进行清零,然后再加1;接着ESI会调用CMP进行比较,说明ESI是循环变量。接下来“MOV AL, BYTE PTR [EBX+ESI]”表示将EBX和ESI相加赋值给AL,然后AL判断一个值再做其他的,这有点像访问数据,后面的显示特性随着AL做改动,即AL影响后面显示的内容。

    MOV指令是数据传送指令,也是最基本的编程指令,用于将一个数据从源地址传送到目标地址(寄存器间的数据传送本质上也是一样的)。

    在这里插入图片描述


    第八步:下面看看寄存器,其中EBX是基址寄存器,ESI是它的偏移量,猜测这个EBX基址寄存器和关键数据有关。

    在这里插入图片描述

    选择EBX基址寄存器,然后选择“数据窗口中跟随”,显示如下图所示的数据。

    在这里插入图片描述

    数据窗口显示如下,我们发现“0F”出现较多,猜测多的这个可能是空的,少的那个是雷“8F”。

    在这里插入图片描述


    第九步:数据区详细分析。
    我们选择0x010026A7位置,然后按下F2取消断点,然后继续运行程序,此时弹出扫雷主界面。游戏中通常会存在边界(围墙),这里“10”应该是边界位置,而0x01005361则为起始位置。

    在这里插入图片描述

    如果第一个不是雷、第二个不是雷、第三个不是雷,第四个才是雷,我们“0F”是空格,“8F”是雷的猜测则正确。

    在这里插入图片描述

    我们取消0x01002700位置的断点,然后运行程序弹出扫雷界面,根据下面的二维矩阵进行扫雷。

    在这里插入图片描述

    如下图所示,前面3个果然时空格,而第四个则时雷。“8A”是雷,“0F”是空格实锤,而且点过的地方会变成数字,比如“40”、“41”、“42”等。

    在这里插入图片描述


    第十步:写个程序进行扫雷数据区详细分析。
    我们重新运行程序,选择“查找”->“当前模块中的名称”,找到双缓存技术的核心函数BitBlt,然后重新找一下,找到代码位置。如下图所示,EBX就是雷区的起始位置,我们要想办法把它读取出来,再往前“MOV EBX, 01005360”代码看到了EBX的赋值定义。

    在这里插入图片描述

    接着我们输入F7单步调试,执行完0x010026C4赋值语句后,我们在数据窗口中跟随EBX寄存器,显示如下图所示。前面8个均为“0F”表示空格,第9个为雷,再验证一次“01005360位置”,就开始准备写程序了。

    在这里插入图片描述

    验证成功,开始写程序吧!

    在这里插入图片描述


    第十一步:扫雷辅助程序。
    我们编写了这样一个程序,当它开启后,我们鼠标移动到方格,如果是雷它的标题会变成“扫雪(xue)”,然后我们不点击它就可以了。哈哈~

    在这里插入图片描述

    正常是“扫雷”。

    在这里插入图片描述

    雷区显示为“扫雪”。当然你也可以写得更隐蔽些,比如和苹果电话手表建立连接,如果是“雷”让手表震动一下,否则正常。

    在这里插入图片描述


    注意,基本原理知道后,就需要开发解决问题了。对于安全行业来说,不管是做病毒还是研究漏洞利用或游戏防护的,逆向分析都是基础,开发解决问题才是关键。比如,某个病毒样本的行为已经分析清楚了,这个病毒在哪里创建系统文件、修改哪个系统文件、注入到哪个进程、动了哪个注册表等等,逆向分析第一步完成,但更重要的是怎么解决问题,创建注册表就需要删除注册表,修改系统文件就要还原文件。

    我们在网上搜索某些病毒资料时,有些逆向工程师会给出手工修复方案,比如关闭哪个服务、删除哪个隐藏文件、手工清除注册表哪一项等。但是对于安全公司来说,比如360公司,你安全扫描完成之后,不可能弹框提示用户手工修复,而是需要提供自动化方案一键修复,最终结果是需要修改杀毒软件的引擎代码,或者提供专杀工具给用户,这个时候工具需要自动化完成相关操作。

    很多新手会看不起开发,觉得搞逆向、搞网安的是王道,不用学开发,这是不对的。 针对上面的实战,我们就发现逆向是站在开发基础上,反向推导作者是怎么做的,比如扫雷需要思考作者会用什么方式表示雷区,然后怎么用UI体现出来以及调用什么函数实现。所以,逆向分析之前都要教开发类的课程,《数据结构》《操作系统》《计算机组成原理》《编译原理》等课程掌握越深入越好。



    三.扫雷游戏检测工具

    通过第二部分,我们知道以下信息:

    • 扫雷的首地址为0x01005360
    • 显示“0F”表示空格,显示“8F”表示雷
    • 雷区的边界为0x10

    原理是找到雷在内存中的值,只要不是雷值我们把它点击开来。接下来作者再补充一个逆向辅助工具,通过CheatEngine实现雷区检测。

    Cheat Engine又称CE修改器,是一款内存修改编辑工具。你可以通过Cheat Engine软件来修改游戏中的内存数据、人物属性、金币数值等等,功能强大且操作简单,可以为你带来良好的更好的体验游戏。


    1.Cheat Engine确定起始位置

    第一步,打开Cheat Engine软件,点击“选择打开一个程序”按钮,如下图所示。

    在这里插入图片描述

    打开扫雷软件设置为初级。

    在这里插入图片描述


    第二步,选择扫描类型为“未知的初始数值”,选择“数值类型”为字节,然后点击“首次扫描”。

    在这里插入图片描述

    此时显示7,290,880个数据,如下图所示:

    在这里插入图片描述


    第三步,接着我们点击扫雷,然后在“扫描类型”中选择“变动的数值”,点击“再次扫描”,此时返回结果183,169个。最终通过反复的筛选找到首地址。

    在这里插入图片描述

    继续点击扫描,如下图所示。

    在这里插入图片描述


    第四步,如果出现地雷则选择“未变动的数值”,点击“再次扫描”,接着继续新一轮的扫雷游戏。

    在这里插入图片描述

    在这里插入图片描述

    始终以第一个方格的状态为目标进行重复的操作。

    • 开始扫描:设置“未知的初始数值”
    • 扫描第一个格子:设置“变动的值”
    • 继续扫描,结果是雷:设置“未变动的值”
    • 继续扫描,结果非雷:设置“未变动的值”
    • 重新开始:设置“变动的值”
    • 重新开始如果第一个格子和上一次一样,则设置“未变动的值”,否则“变动的值”

    下图展示了最后5个结果,接着继续反复筛选。

    在这里插入图片描述

    最终获取如下图所示的结果,其初始地址为——0x01005361,和前面OD软件分析的一样。

    在这里插入图片描述


    第五步,双击该行移动至底部,然后右键选择“浏览相关内存区域”选项。

    在这里插入图片描述

    显示内容如下图所示,它同样和我们前面OD分析的内容一致。其中“8F”表示雷,“40”表示空格,“41”到“49”表示数字,“10”表示边界,同时“0F”表示隔一行。

    在这里插入图片描述

    如下图所示,成功完成了扫雷游戏,O(∩_∩)O

    在这里插入图片描述

    在这里插入图片描述


    2.Cheat Engine确定边界

    第一步,我们查看扫雷初级的高度是“9”,然后重新打开建立“新的扫描”。

    在这里插入图片描述

    输入数字“9”再点击“首次扫描”,返回7174个结果。

    在这里插入图片描述


    第二步,选择中级难度,对应的高度是“16”,然后重新打开建立“再次扫描”,仅剩4个结果。

    在这里插入图片描述

    在这里插入图片描述


    第三步,选择高级难度,设置高度最高即“24”,然后重新打开建立“再次扫描”

    在这里插入图片描述

    最终剩2个结果,高度可能是:

    • 0x01005338
    • 0x010056A8

    在这里插入图片描述


    第四步,使用同样的方法找到宽度。
    宽度返回两个结果:

    • 0x01005334
    • 0x010056AC

    在这里插入图片描述



    3.C++编写鼠标坐标获取案例

    接下来我们开始编写代码,首先给大家看看鼠标坐标获取的一段代码,我们鼠标通常是(x, y)的形式。

    第一步,创建空项目,名称为“MouseMsg”。

    在这里插入图片描述


    第二步,为该工程添加一个“main.cpp”文件,并且添加启动项。

    在这里插入图片描述

    在这里插入图片描述


    第三步,配置graphics.h文件。
    graphics.h是一个针对Windows的C语言图形库,分为像素函数、直线和线型函数、多边形函数、填充函数等。在学习C++游戏编程时,通常会发现VS中没有”graphics.h”头文件,因此需要配置。

    (1) 先从作者github中下载好所需要的文件,如下所示:

    在这里插入图片描述

    下载完后打开下载好的 Inlcude 文件夹,里面有两个头文件:

    在这里插入图片描述

    (2) 将里面的两个文件进行复制,然后粘贴到VS安装目录的include文件夹中。

    • C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.26.28801\include

    在这里插入图片描述

    (3) 打开下载好的文件夹中的 lib2015子文件夹,将里面的东西全部选中复制,粘贴到VS2015安装目录的 lib 文件夹中。

    • C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.26.28801\lib\x86
    • C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.26.28801\lib\x64

    在这里插入图片描述
    在这里插入图片描述

    (4) 接下来就可以在VS中添加“graphics.h”头文件了,里面有很丰富的绘图函数可供我们使用。

    #include <graphics.h>              // 引用图形库头文件
    #include <conio.h>
    
    int main()
    {
    	initgraph(640, 480);            // 创建绘图窗口,大小为 640x480 像素
    	setlinecolor(RGB(255, 0, 0));   // 设置当前线条颜色
    	setfillcolor(RGB(0, 255, 0));   // 设置当前填充颜色
    	fillcircle(200, 200, 100);      // 画圆,圆心(200, 200),半径 100
    	_getch();                       // 按任意键继续
    	closegraph();                   // 关闭图形环境
    }
    

    在这里插入图片描述


    第四步,编写鼠标事件代码。

    #include <graphics.h>              // 引用图形库头文件
    #include <stdio.h>
    
    int main()
    {	
    	//定义鼠标
    	MOUSEMSG m;
    	//初始化窗口 500宽度 500高度
    	initgraph(500, 500);
    
    	while (1) {
    		//获取鼠标消息
    		m = GetMouseMsg();
    		char buff[256];
    
    		//鼠标左键按下
    		if (m.uMsg == WM_LBUTTONDOWN) {
    			//清空数组
    			memset(buff, 0, 256);
    			sprintf_s(buff, "X坐标:%d, Y坐标:%d", m.x, m.y);
    			MessageBox(NULL, buff, "坐标", MB_OK);
    		}
    	}
    	return 0;
    }
    

    运行前需要设置编码方式为“使用多直接字符集”,否则会报错。

    在这里插入图片描述

    运行结果如下图所示,可以看到鼠标点击会显示对应的坐标位置。

    在这里插入图片描述

    其中GetMouseMsg函数表示获取鼠标消息,通过Spy++可以看到很多Windows系统自带的鼠标操作、键盘操作、消息操作等,同时能获取鼠标是左键或右键按下以及对应坐标。

    在这里插入图片描述



    4.C++编写自动扫雷程序

    接下来是通过C++实现一键扫雷功能,主要是模拟鼠标在雷区的点击操作,并且按下所有非雷区域从而实现一键扫雷。利用的是Windows应用程序的消息机制,通过SendMessage函数向指定窗口发送消息,也就是在获取到扫雷的窗口句柄后,利用这个函数向该窗口发送鼠标按键消息,从而实现模拟鼠标的操作。

    该部分参考ioio_jy老师的文章:逆向工程第007篇:扫雷辅助的研究——0秒实现一键自动扫雷

    第一步,创建一个应用程序名叫“SaoleiHelp”,并添加主函数。

    在这里插入图片描述


    第二步,分析扫雷的区域及坐标定义。
    坐标是基于客户区的左上角,但是这个客户区是怎么定义的呢?
    如下图所示,究竟A点是客户区的左上角,还是说B点才是呢?如果A点为坐标原点,那么第一块雷区的坐标就应为(AC,CE),如果B点为坐标原点,那么第一块雷区的坐标就应为(BD,DE)。经过实际测试,MSDN中所谓的客户区,其实是以B点作为起点的位置,即原点坐标(0,0),而雷区中心即E点的坐标为(16,61),每个雷区小方块的大小为16×16,于是可以知道,这里需要循环计算出雷区每一个小方块的坐标,这个坐标与保存有雷区的二维数组下标紧密相关。

    在这里插入图片描述

    假设这个二维数组是mine[y1][x1],其中y1表示的是雷区有多少行,x1表示雷区的列数,那么每个雷区方块的坐标为:

    x = x1 * 16 + 16;
    y = y1 * 16 + 61;
    

    在获得了坐标以后,就可以通过如下语句来模拟鼠标的点击操作了:

    SendMessage(hWnd, WM_LBUTTONDOWN, MK_LBUTTON, MAKELONG(x, y));
    SendMessage(hWnd, WM_LBUTTONUP, MK_LBUTTON, MAKELONG(x, y));
    

    第三步,分析扫雷游戏的雷区长宽数据。
    结合之前宽度、高度的分析,发现高度位置为0x01005338,宽度位置为0x01005334。我们进一步推断,从0x01005330开始,这里的一行绿色数据包含有0x0A、0x09以及0x09这三个数值,很明显这三个数据正是当前雷区的地雷数量以及宽、高等信息

    • 雷数:0x01005330
    • 宽度:0x01005334
    • 高度:0x01005338

    在这里插入图片描述

    同时,我们上面的逆向分析已经知道雷区分布的信息。

    • “8F”表示地雷
    • “8E”表示旗子
    • “40”表示空格
    • “41”到“49”表示数字
    • “10”表示边界
    • “0F”表示隔一行

    还有一个重要信息是雷区的分布起始地址,即:

    • 0x01005361

    但如果计算含有边界的情况,雷区的分布情况则为:

    • 起始地址:0x01005340
    • 结束地址:0x0100567F

    在这里插入图片描述


    完整代码如下:

    #include <stdio.h>
    #include <windows.h>
    #include <graphics.h>
    
    int main() {
    	DWORD Pid = 0;
    	HANDLE hProcess = 0;
    
    	DWORD result1, result2;
    
    	// 获取扫雷游戏对应的窗口句柄
    	HWND hWnd = FindWindow(NULL, L"扫雷");
    	if (hWnd != 0) {
    		// 获取扫雷进程ID
    		GetWindowThreadProcessId(hWnd, &Pid);
    		// 打开扫雷游戏获取其句柄
    		hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);
    		if (hProcess == 0) {
    			printf("Open winmine process failed.");
    			return 0;
    		}
    
    		// 存放雷区的起始地址
    		DWORD dwBoomAddr = 0x01005340;
    
    		// 雷区的最大值(包含边界)
    		DWORD dwSize = 832;
    		PBYTE pByte = NULL;
    		pByte = (PBYTE)malloc(dwSize);
    
    		// 读取整个雷区的数据
    		ReadProcessMemory(hProcess, (LPVOID)dwBoomAddr, pByte, dwSize, 0);
    		int i = 0;
    		int j = 0;
    		int n = dwSize;
    
    		// 读取雷区的长和宽
    		DWORD dwInfo = 0x01005330;
    		DWORD dwHeight = 0, dwWidth = 0;
    		ReadProcessMemory(hProcess, (LPVOID)(dwInfo + 4), &dwWidth, sizeof(DWORD), 0);    //宽度
    		ReadProcessMemory(hProcess, (LPVOID)(dwInfo + 8), &dwHeight, sizeof(DWORD), 0);   //高度
    
    		int h = dwHeight;
    		int count = 0;
    
    		// 雷区转换,去掉雷区多余的数据
    		PBYTE pTmpByte = NULL;
    		pTmpByte = (PBYTE)malloc(dwHeight * dwWidth);
    		while (i < dwSize) {
    			//边界判断
    			if (pByte[i] == 0x10 && pByte[i + 1] == 0x10) {
    				i = i + dwWidth + 2;
    				continue;
    			}
    			else if (pByte[i] == 0x10) {
    				for (j = 1; j <= dwWidth; j++) {
    					pTmpByte[count] = pByte[i + j];
    					count++;
    				}
    				i = i + dwWidth + 2;
    				continue;
    				h--;
    				if (h == 0) break;
    			}
    			i++;
    		}
    
    		// 获取雷区方块的坐标,然后模拟鼠标进行点击
    		int x1 = 0, y1 = 0;
    		int x = 0, y = 0;
    		for (i = 0; i < dwHeight * dwWidth; i++) {
    			if (pTmpByte[i] != 0x8F) { //雷
    				x1 = i % dwWidth;
    				y1 = i / dwWidth;
    				x = x1 * 16 + 16;
    				y = y1 * 16 + 61;
    				SendMessage(hWnd, WM_LBUTTONDOWN, MK_LBUTTON, MAKELONG(x, y));   //鼠标按下
    				SendMessage(hWnd, WM_LBUTTONUP, MK_LBUTTON, MAKELONG(x, y));     //鼠标抬起
    			}
    		}
    
    		free(pByte);
    		CloseHandle(hProcess);
    	}
    	else {
    		printf("Get hWnd failed.");
    	}
    	return 0;
    }
    

    运行结果如下图所示,一秒实现扫雷。

    在这里插入图片描述



    四.总结

    写到这里,这篇文章就介绍完毕,希望对您有所帮助,最后进行简单的总结下。

    • 一.什么是逆向分析
      1.逆向工程
      2.逆向分析的典型应用
    • 二.扫雷游戏逆向分析
      1.游戏介绍
      2.OllyDbg动态分析
    • 三.扫雷游戏检测工具
      1.Cheat Engine确定起始位置
      2.Cheat Engine确定边界
      3.C++编写鼠标坐标获取案例
      4.C++编写自动扫雷程序

    学安全一年,认识了很多安全大佬和朋友,希望大家一起进步。这篇文章中如果存在一些不足,还请海涵。作者作为网络安全初学者的慢慢成长路吧!希望未来能更透彻撰写相关文章。同时非常感谢参考文献中的安全大佬们的文章分享,深知自己很菜,得努力前行。

    在这里插入图片描述

    《珈国情》
    明月千里两相思,
    清风缕缕寄离愁。
    燕归珞珈花已谢,
    情满景逸映深秋。
    最感恩的永远是家人的支持,知道为啥而来,知道要做啥,知道努力才能回去。夜已深,虽然笨,但还得奋斗。

    (By:Eastmount 2020-07-26 星期一 晚上9点写于武汉 http://blog.csdn.net/eastmount/ )



    2020年8月18新开的“娜璋AI安全之家”,主要围绕Python大数据分析、网络空间安全、人工智能、Web渗透及攻防技术进行讲解,同时分享CCF、SCI、南核北核论文的算法实现。娜璋之家会更加系统,并重构作者的所有文章,从零讲解Python和安全,写了近十年文章,真心想把自己所学所感所做分享出来,还请各位多多指教,真诚邀请您的关注!谢谢。

    参考文献:
    真心推荐大家好好看看这些视频和文章,感恩这些大佬!
    [1] 科锐逆向的钱林松老师受华中科技大学邀请- “逆向分析计算引导”
    [2] c++学习笔记——VS2015中添加graphics.h头文件 - 行歌er
    [3] 逆向工程第007篇:扫雷辅助的研究——0秒实现一键自动扫雷
    [4] https://www.bilibili.com/video/BV18W411U7NH
    [5] [网络安全自学篇] 五.IDA Pro反汇编工具初识及逆向工程解密实战
    [6] [网络安全自学篇] 六.OllyDbg动态分析工具基础用法及Crakeme逆向

    展开全文
  • 在讲解扫雷代码前,先来介绍一下扫雷是个什么游戏,在没要用C语言写出扫雷之前我其实连扫雷是什么都不知道,儿时玩扫雷的时候就是一通瞎点,然后砰!的一下炸,游戏就失败了 扫雷的游戏界面是这样的: 最左边红色的...

    有幸在鹏哥的指导下写出了扫雷游戏,相比于上一次的三子棋代码,扫雷代码更加考验一个程序员对于数组的灵活运用,也再一次激起了我对代码的兴趣和热情

    在讲解扫雷代码前,先来介绍一下扫雷是个什么游戏,在没要用C语言写出扫雷之前我其实连扫雷是什么都不知道,儿时玩扫雷的时候就是一通瞎点,然后砰!的一下炸,游戏就失败了

    扫雷的游戏界面是这样的:
    在这里插入图片描述
    最左边红色的数字就是这个棋盘里面埋了99个雷,右边红色的数字代表的是玩家所进行的时间

    在这里插入图片描述
    运气不好,点了两下就死掉了

    调节一下游戏难度在这里插入图片描述
    现在这就是一个10x10的一个边框,里面含有10个雷的棋盘

    这里先点击一下,出现了一个1
    在这里插入图片描述
    这个1的意思就是指,以它为中心的八个格子中有一颗地雷在附近
    (因为没有找到合适的效果就重开了一把,大概就是这样)
    在这里插入图片描述

    看!这个游戏已经失败了,显示出来的雷中在1的周围只有1个地雷,数字1的意思就是在1周围的8x8的格子周围中只有一个雷(红笔所标示的)

    当将所有没有雷的地方全部猜中,棋盘空间里的地方只剩下来雷的时候,就代表这个游玩玩家胜利
    在这里插入图片描述
    如图所示,8个1里围着一块空间,那这个空间的就只能是雷,棋盘已经除了是雷的地方已经全部被占满了,系统也提示了“you win!”的提示

    那讲到这,扫雷的大致游戏原理我们就已经差不多明白了,接下来就开始实现它

    首先引用头文件,输入主函数,自定义函数SAOLEI(我是学西班牙语的,英语菜的和屎一样)

    #include<stdio.h>
    int main()
    {
    	SAOLEI();
    	return 0;
    }
    

    在做这一款扫雷游戏前我们先要做出这个游戏的游戏菜单,用do…while循环和switch分支的写法,来达到玩家玩完游戏后再玩一次和提供玩家选择的菜单效果(具体的可以看我上一篇博客中写道的三子棋,这里简要代过)

    void menu()
    {
    	printf("扫雷游戏\n");
    	printf("1,开始游戏\n");
    	printf("0,结束游戏\n");
    }
    
    void SAOLEI()
    {
    	int inpunt = 0;
    	srand((unsigned int)time(NULL));
    	do
    	{
    		menu();
    		printf("请输入数字");
    		scanf("%d", &inpunt);
    		switch (inpunt)
    		{
    		case 1:
    			Saolei_s();
    			break;
    		case 0:
    			printf("结束游戏\n");
    			break;
    		default:
    			printf("输入错误,请重新输入\n");
    			break;
    		}
    
    	} while (inpunt);
    }
    

    测试一下,大概的效果就是这样在这里插入图片描述
    在这里插入图片描述
    可以根据自己的需求来制定界面,接下来就在case1的选项中自定义函数Saolei_s()来开始制作游戏内容

    游戏内容我们分成两个步骤来进行说明:1,制作棋盘2,开始扫雷

    我们这里来制作一个10x10的棋盘,但在这之前,我们先要搞清楚棋盘到底是一个什么样子的结构,通过我一开始的扫雷游戏讲解我们不难推出,扫雷这个游戏的棋盘组成是由两个棋盘构成的,分别是玩家输入的棋盘还有埋雷的棋盘
    在这里插入图片描述
    在这里插入图片描述
    换一种理解方式的话,我们可以理解为:普通的笑脸是一个棋盘,而哭脸也是一个棋盘,我们把这两个棋盘都设置为数组,当输入的坐标是哭脸的雷的时候,我们就把它反映在笑脸的这个棋盘上,转成哭脸棋盘,就显示了失败的样子
    同时我们还要注意的是,这个格子虽然看上去只有10x10,但要注意我们每输入一个坐标,就要显示周围的地雷数,如果就会显示出周围的地雷数,可如果我们输入的边框的坐标,就像这样:
    在这里插入图片描述
    那这里计算的也是他附近8x8的坐标,可是由于是边框位置的受限,它只计算了附近三个坐标的地雷数,实际上它计算的还是8个在这里插入图片描述
    因此我们不难推出,这个棋盘实际的模样是这样的:
    在这里插入图片描述
    一个9x9的棋盘,我们需要一个11x11的棋盘来作为内棋盘(用来放置地雷和计算周围地雷数),还需要一个9x9的棋盘用来给玩家进行操作

    首先再一次定义主函数

    #define ROW 9
    #define COL 9
    #define ROWS ROW+2
    #define COLS COL+2
    

    为了方便打印和更改数字,我们就将其提前定义,同理既然是在原莱的树上进行扩大,我们也在原来的数字上加2即可

    接下来在刚刚菜单分支一中自定义函数Saolei_s

    void Saolei_s()
    {
    	printf("开始游戏\n");
    	//布置好棋盘
    	char board[ROWS][COLS] = { 0 };
    	//布置好棋盘
    	char show[ROWS][COLS] = { 0 };
    	boards(board, ROWS, COLS, '0');//初始化棋盘
    	boards(show, ROWS, COLS, '*');
    	//打印棋盘
    	//DLYs_board(board, ROW, COL);这一个是内置雷区,用来布置雷
    	DLYs(show, ROW, COL);//只需要打印中间的那一圈(9X9)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
    	//布置地雷
    	BUZHILEI(board, ROW, COL);
    	/*DLYs(board, ROW, COL);*///打印确认一下是否已经布置完毕
    	//扫描地雷
    	POOMPOOMPOOM(board, show, ROW, COL);}
    }
    

    定义里面布满地雷的棋盘(下面简称为雷盘,board)和玩家游玩的棋盘(接下来称为玩盘,show),然后开始棋盘的初始化,我们将雷盘放满0,玩盘放满1

    void boards(char board[ROWS][COLS], int rows, int cols, char ret)
    {
    	int i = 0;
    	int j = 0;
    	for (i = 0; i < rows; i++)
    	{
    		for (j = 0; j < cols; j++)
    		{
    			board[i][j] = ret;
    		}
    	}
    }
    

    为了不定义两个函数这么浪费空间,我们在这个函数上多引用一个char来放置我们要给雷盘和玩盘放入的东西

    接下来我们打印棋盘,自定义函数DLYs

    这里要留意的是,我们引入的整形是row和col,因为雷出现的范围只能是在9x9的格子内,如果出现在11x11的盘子上,则会出现误导玩家的情况玩家的操作空间只能是在9x9中
    我们这里操作的还是雷盘(board),注意后面跟着的是11x11

    void BUZHILEI(char board[ROWS][COLS], int row, int col)
    
    void DLYs(char board[ROWS][COLS], int row, int col)
    {
    	int i = 0;
    	int j = 0;
    	for (i = 1; i <= row; i++)
    	{
    		for (j = 1; j <= col; j++)
    		{
    			printf("%c ", board[i][j]);
    		}
    		printf("\n");
    	}
    }
    

    i从1开始,小于等于row/col,就把棋盘限制在了9x9中

    打印出来看看效果
    在这里插入图片描述
    这样看感觉目不接暇,很难第一眼就找出我想要的坐标,不如在打印这些棋盘前先打印出数字怎么样
    修改代码

    void DLYs(char board[ROWS][COLS], int row, int col)
    {
    	int i = 0;
    	int j = 0;
    	printf("  ");
    	for (i = 1; i <= col; i++)
    	{
    		printf("%d ", i);
    	}
    	printf("\n");
    	for (i = 1; i <= row; i++)
    	{
    		printf("%d ", i);
    		for (j = 1; j <= col; j++)
    		{
    			printf("%c ", board[i][j]);
    		}
    		printf("\n");
    	}
    }
    

    再来看看打印效果
    在这里插入图片描述
    测试没问题

    接下来开始布置地雷,自定义函数BUZHILEI,,代入量和上面的一样
    设ZD等于地雷的数量Poom,Poom放入头文件中定义,方便修改

    void BUZHILEI(char board[ROWS][COLS], int row, int col)
    {
    	int ZD = Poom;
    	while (ZD)
    	{
    
    		int x = rand() % row + 1;
    		int y = rand() % col + 1;
    		if (board[x][y] == '0')
    		{
    			board[x][y] = '1';//这里只有一个等于!
    			ZD--;
    		}
    
    	}
    }
    

    引入时间戳和随机值

    #define _CRT_SECURE_NO_WARNINGS 1
    #include<stdio.h>
    #include<stdlib.h>
    #include<time.h>
    
    #define ROW 9
    #define COL 9
    #define ROWS ROW+2
    #define COLS COL+2
    #define Poom 10
    
    

    在定义x和y放入随机值,设置循环,循环里面放入ZD,因为雷盘中布置的全是0,所以只要随机值的坐标是0,就把雷放进去,然后ZD减去1,当ZD等于0的时候,循环跳出,雷就布置完毕

    打印出来看看效果
    在这里插入图片描述
    接下来开始定义函数扫雷,用于玩家操作

    
    void POOMPOOMPOOM(char board[ROWS][COLS], char show[ROWS][COLS], int row, int col)
    {
    	//先输入玩家的坐标
    	int x = 0;
    	int y = 0;
    	int win = 0;
    	
    	while (win<row*col-Poom)
    	{
    		int count = 0;
    		printf("请输入排查坐标:>");
    		scanf("%d%d", &x, &y);
    		
    		//如果坐标存在
    		if (x >= 1 && x <= row && y >= 1 && y <= col)
    		{
    			if (board[x][y] == '1')//如果触发雷
    			{
    				show[x][y] = '1';
    				printf("排查失败,你触发了地雷\n");
    				DLYs(show, ROW, COL);
    				DLYs(board, ROW, COL);
    				break;
    			}
    			else//如果没有触发雷
    			{
    				//计算输入坐标周围的地雷数
    				int count = get_mine(board, x, y);
    				show[x][y] = count + '0';
    				DLYs(show, ROW, COL);
    				win++;
    			
    			}
    		}
    		else
    		{
    			printf("输入坐标错误,请重新输入\n");
    		}
    		
    	}
    	
    	if (win==row * col - Poom)
    	{
    		printf("恭喜你,排雷成功!\n");
    		DLYs(board, ROW, COL);
    	}
    }
    

    这个函数比较复杂,我们一步一步讲

    首先我们要把两个棋盘都放入函数中,并且放入row和col

    设置三个变量,分别是x和y来给玩家输入坐标,设置win来做判断条件

    设置while循环,如果win<row*col-poom,也就是说当win等于71的时候,我们就跳出循环,row和col还有Poom都是我们设的数字,71就是除了雷之外玩家可以排除的坐标(总共坐标有81个)

    玩家输入的坐标有三种情况:输入坐标无雷,输入坐标有雷,输入坐标错误

    先定义输入坐标有雷无雷的,设置条件,将x和y的范围限制在1和row/col之间,然后如果输入的坐标是雷盘的1,那就是触发地雷,失败

    if (x >= 1 && x <= row && y >= 1 && y <= col)
    		{
    			if (board[x][y] == '1')//如果触发雷
    			{
    				show[x][y] = '1';
    				printf("排查失败,你触发了地雷\n");
    				DLYs(show, ROW, COL);
    				DLYs(board, ROW, COL);
    				break;
    			}
    

    若不是,则打印出来周围的数字,定义变量count,将雷盘和xy放入自定义整形函数get_mine中

    else//如果没有触发雷
    			{
    				//计算输入坐标周围的地雷数
    				int count = get_mine(board, x, y);
    
    int get_mine(char board[ROWS][COLS], int x, int y)
    {
    	return board[x - 1][y] + board[x - 1][y + 1] + board[x - 1][y - 1] +
    		board[x][y + 1] + board[x][y - 1] + board[x + 1][y]+
    		board[x + 1][y + 1] + board[x + 1][y - 1] - 8 * '0';//减去8个字符0
    }
    

    注意,函数内操作的是一个字符,根据阿斯克码表,每一个字符对应的数字是不一样的在这里插入图片描述
    字符0对应的是48,1对应的是49…所以,为了能返回一个真确的数字,我们要减掉8个字符‘0’(因为旁白有8个格子),返回的是一个雷数,但是这个数字是一个字符

    因为我们要显示出这个字符(根据扫雷玩法,我们要显示出这个坐标上周围的雷数),我们就要在玩盘上对应的坐标来打印出周围的雷数,因此

    else//如果没有触发雷
    			{
    				//计算输入坐标周围的地雷数
    				int count = get_mine(board, x, y);
    				show[x][y] = count + '0';
    				DLYs(show, ROW, COL);
    				win++;
    			
    			}
    

    当输入一个坐标且不是雷的时候,win就加1,来推动玩家的游戏进度(循环条件)
    在循环内,若是玩家输入错误坐标(9x9的格子里不可能出现(10,10)的坐标,我们就给玩家提示)

    else
    		{
    			printf("输入坐标错误,请重新输入\n");
    		}
    

    最后,当玩家跳出循环时,我们在函数内的循环外下给玩家庆祝:游戏胜利

    if (win==row * col - Poom)
    	{
    		printf("恭喜你,排雷成功!\n");
    		DLYs(board, ROW, COL);
    	}
    

    这样子,这个扫雷游戏就制作完毕,最后给大家分享一下源代码

    #define _CRT_SECURE_NO_WARNINGS 1
    #include<stdio.h>
    #include<stdlib.h>
    #include<time.h>
    
    #define ROW 9
    #define COL 9
    #define ROWS ROW+2
    #define COLS COL+2
    #define Poom 10
    
    void DLYs(char board[ROWS][COLS], int row, int col)
    {
    	int i = 0;
    	int j = 0;
    	printf("  ");
    	for (i = 1; i <= col; i++)
    	{
    		printf("%d ", i);
    	}
    	printf("\n");
    	for (i = 1; i <= row; i++)
    	{
    		printf("%d ", i);
    		for (j = 1; j <= col; j++)
    		{
    			printf("%c ", board[i][j]);
    		}
    		printf("\n");
    	}
    }
    
    int get_mine(char board[ROWS][COLS], int x, int y)
    {
    	return board[x - 1][y] + board[x - 1][y + 1] + board[x - 1][y - 1] +
    		board[x][y + 1] + board[x][y - 1] + board[x + 1][y]+
    		board[x + 1][y + 1] + board[x + 1][y - 1] - 8 * '0';//减去8个字符0
    }
    
    void POOMPOOMPOOM(char board[ROWS][COLS], char show[ROWS][COLS], int row, int col)
    {
    	//先输入玩家的坐标
    	int x = 0;
    	int y = 0;
    	int win = 0;
    	
    	while (win<row*col-Poom)
    	{
    		int count = 0;
    		printf("请输入排查坐标:>");
    		scanf("%d%d", &x, &y);
    		
    		//如果坐标存在
    		if (x >= 1 && x <= row && y >= 1 && y <= col)
    		{
    			if (board[x][y] == '1')//如果触发雷
    			{
    				show[x][y] = '1';
    				printf("排查失败,你触发了地雷\n");
    				DLYs(show, ROW, COL);
    				DLYs(board, ROW, COL);
    				break;
    			}
    			else//如果没有触发雷
    			{
    				//计算输入坐标周围的地雷数
    				int count = get_mine(board, x, y);
    				show[x][y] = count + '0';
    				DLYs(show, ROW, COL);
    				win++;
    			
    			}
    		}
    		else
    		{
    			printf("输入坐标错误,请重新输入\n");
    		}
    		
    	}
    	
    	if (win==row * col - Poom)
    	{
    		printf("恭喜你,排雷成功!\n");
    		DLYs(board, ROW, COL);
    	}
    }
    void BUZHILEI(char board[ROWS][COLS], int row, int col)
    {
    	int ZD = Poom;
    	while (ZD)
    	{
    
    		int x = rand() % row + 1;
    		int y = rand() % col + 1;
    		if (board[x][y] == '0')
    		{
    			board[x][y] = '1';//这里只有一个等于!
    			ZD--;
    		}
    
    	}
    }
    
    
    
    void boards(char board[ROWS][COLS], int rows, int cols, char ret)
    {
    	int i = 0;
    	int j = 0;
    	for (i = 0; i < rows; i++)
    	{
    		for (j = 0; j < cols; j++)
    		{
    			board[i][j] = ret;
    		}
    	}
    }
    void Saolei_s()
    {
    	int PD = 0;
    	printf("开始游戏\n");
    	//布置好棋盘
    	char board[ROWS][COLS] = { 0 };
    	//布置好棋盘
    	char show[ROWS][COLS] = { 0 };
    	boards(board, ROWS, COLS, '0');//初始化棋盘
    	boards(show, ROWS, COLS, '*');
    	//打印棋盘
    	//DLYs_board(board, ROW, COL);这一个是内置雷区,用来布置雷
    	DLYs(show, ROW, COL);//只需要打印中间的那一圈(9X9),需要将整个11X11提取出来
    	//布置地雷
    	BUZHILEI(board, ROW, COL);
    	/*DLYs(board, ROW, COL);*///打印确认一下是否已经布置完毕
    	//扫描地雷
    	POOMPOOMPOOM(board, show, ROW, COL);
    
    }
    void menu()
    {
    	printf("扫雷游戏\n");
    	printf("1,开始游戏\n");
    	printf("0,结束游戏\n");
    }
    
    void SAOLEI()
    {
    	int inpunt = 0;
    	srand((unsigned int)time(NULL));
    	do
    	{
    		menu();
    		printf("请输入数字");
    		scanf("%d", &inpunt);
    		switch (inpunt)
    		{
    		case 1:
    			Saolei_s();
    			break;
    		case 0:
    			printf("结束游戏\n");
    			break;
    		default:
    			printf("输入错误,请重新输入\n");
    			break;
    		}
    
    	} while (inpunt);
    }
    
    int main()
    {
    	SAOLEI();
    	return 0;
    }
    

    一个小小的扫雷,里面居然蕴含了如此丰富的知识和细节,flash player已在2020年12月不再支持,对于那个时候在网页上的游戏制作者创作出简陋的游戏可却趣味十足,在这里,深深对他们表示敬意和对我童年快乐的感谢
    希望有一天,我也能像他们一样,写出可以让大家变得开心的程序

    展开全文
  • 娜璋AI安全之家于2020年8月18日开通,将专注于Python和安全技术,主要...第一篇文章先带领大家学习什么是逆向分析,然后详细讲解逆向分析的典型应用,接着通过OllyDbg工具逆向分析经典的游戏扫雷,再通过Cheat Engin
  • 经典证明:扫雷是NP完全问题

    千次阅读 2008-07-04 00:02:00
    曾经看到过自动扫雷软件,当时我就在想,扫雷游戏是否有什么牛B的多项式算法。最近才看到,扫雷问题居然一个NP完全问题,并且这个定理有一个简单、直观而又神奇的证明。在这里和大家分享一下整个证明过程。 首先...
  • Java扫雷!!! 扫雷

    2010-01-25 12:07:57
    用Java写的扫雷我自己在学习Java时写的,现在看有点问题!大家帮我看看,发现什么问题,给我发邮件,我的邮箱liang.wang.hubei@gmail.com。大家一起进步,谢谢!!!
  • 此C语言程序“扫雷”经供参考,如果有什么意义请联系
  • 扫雷游戏

    2018-01-05 22:36:38
    以下关于扫雷游戏的代码,有什么问题还请大神们指教。 头文件game.h #ifndef __GAME_H__ #define __GAME_H__ #include #include #include #include #define COUNT 10//你布下的雷数 #define COL 9 #define ...
  • 真的很简易的扫雷~

    2018-11-05 19:41:05
    还存在一些问题,比如格子只能一个一个打开,不能把附近安全的格子打开(这个等我在研究研究),所以这个扫雷地图只有5*5大小,只有5个雷,后续高级功能敬请期待(虽然我也不知道什么时候会改进!) 以下几个方面...
  • 游戏扫雷源码

    2014-08-25 17:07:31
    本人用最笨的办法向大家介绍扫雷游戏的开发 里面附赠有扫雷游戏开发步骤。源码备注很详细 。不用什么大的算法,只用最简单的推理 。有兴趣的可以看下
  • 在VR里扫雷是什么感受?难道会被炸上天和太阳肩并肩吗? 经典游戏VR化,好像正在成为一种发展趋势。前阵子,丹麦游戏工作室Dreamcave Studio推出了类似《贪吃蛇》的VR游戏,现在Windows自带的《扫雷》也有了...
  • 扫雷源代码

    2013-05-22 19:31:14
    个带有一些注释的扫雷源程序,可以直接在VC下运行。作为学生的实验报告什么的最好了。
  • C语言之扫雷

    万次阅读 2018-03-20 17:52:26
    前言 用C语言写出一个扫雷程序。像大家熟悉的电脑扫雷一样,如果选的位置有雷就结束游戏;如果没有雷,就标记出来...首先需要写一个展示功能,否则玩家拿什么扫 其次,要对雷区进行随机布雷。这里布雷的要求第一...
  • 2127: 扫雷

    2020-11-27 00:55:30
    sc和bjw在玩扫雷, 但是sc非常菜,他不知道扫雷游戏方格上的数字代表什么含义, bjw就给他做了详细的解释 方格上的数字为1,代表这个方格一周只有一个雷 如图 通过这个规则,我们可以知道红圈圈出的位置方框都地雷 但是...
  • c语言扫雷程序

    千次阅读 2017-03-15 22:35:24
    观察Windwos的扫雷游戏,可以知道,一个格子有两种类型的状态需要描述,一种类型这个格子是否有雷,另外一种状态格子的绘制状态,这个类型就多了,格子上可能显示未知,数字,空白,旗帜,疑问,雷,一共6种...
  • 全文共3342字,预计学习时长10分钟来源:Pexels我们常常认为在预处理文本时,去除停用词很明智的一种操作。的确,我同意这一做法,但是我们应该谨慎决定该去除哪类停用词。比如说,...
  • C数组实现扫雷

    2019-06-11 21:24:56
    扫雷游戏的实现需要两个数组,假设我们要实现一个10 * 10的扫雷游戏,我们得定义两个 12 * 12的数组,一个Map[12][12],一个mine[12][12] 其中Map[12][12]的作用就是给玩家看,mine[12][12]的作用用来布雷;当...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 398
精华内容 159
关键字:

扫雷是什么