扫雷,但无system("cls")!

没错,我今天又无聊写了个扫雷。
但无system(“cls”)!不废眼睛!渣机也能玩!
你们可能觉得是我跟system(“cls”)有仇,还是说不会用?其实都不是,只是适用的场景不同罢了:

system(“cls”)适用于要重新输出的时候,比如游戏结束时清屏输出“game over”;

覆盖输出适用于要更新状态的时候,比如对象的位置发生了改变,覆盖输出移动前的位置和移动后的位置。

一般(我)写游戏用覆盖输出更好;
不闪屏,效率高,只需要抓住哪变了、变哪了,接着覆盖输出更新就行,还可以锻炼思维。
下面就是源码,想试玩、学习的可以看看或丢编译器里:

#include<bits/stdc++.h>
#include<windows.h>
#include<conio.h>
using namespace std;

/*
我写的游戏规则:
wasd或方向键控制光标进行操作,
空格翻格子,q插/拆旗,
只有非雷的格子全点了才胜利,你们是逃不掉2选1的,
剩下的跟正常的扫雷没区别(第一个翻开的位置不会有雷)。
*/

//定义结构体存坐标
struct axis{
    int x,y;
};

//自己做的非小数数字转字符串
string string_num(long long num){
    string str;
    bool pd=num<0;
    num=abs(num);
    do{
        str=char(num%10+'0')+str;
        num/=10;
	}while(num);
    if(pd)str='-'+str;
    return str;
}

//设置控制台长和宽
void systemcl(long long length,long long width){
	string str="mode con:cols="+string((length<15)?"15":string_num(length))+" lines="+string_num(width);//设置控制台长和宽
    const int strlen=str.size()+1;//定义一个str长度+1的整型常量
	char chars[strlen];//定义一个长度为str长度+1的char类型的数组 
    for(int i=0;i<str.size();i++)chars[i]=str[i];//因为system只能接受char数组,所以要转换
    chars[str.size()]='\0';//必须加,不然会无效指令
    system(chars);//有些电脑可能设置不了(我也不知道为什么) 
}

//覆盖输出核心功能函数
//让光标移动到控制台指定位置(从0,0开始)
void setCursorPosition(int x, int y){
    COORD coord;
    coord.X=x,coord.Y=y;
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),coord);
}

//让输出变色  
void color_print(int color,int color2){
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),color+color2*16);
}

//隐藏、显示光标
void hidden(bool pd){
    CONSOLE_CURSOR_INFO cci;
    GetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE),&cci);
    cci.bVisible=pd;//赋1为显示,赋0为隐藏
    SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE),&cci);
}

//修复了随机数产生了比max(a,b)大的数,而且修复了每个数产生的比例不等的情况
long long random(long long a,long long b){
    return rand()*1.0/(RAND_MAX+1)*(max(a,b)-min(a,b)+1)+min(a,b);
}

//撒随机数种子
void srs(){
	SYSTEMTIME c;
   	GetSystemTime(&c);
    srand(time(0)*1000+c.wMilliseconds);//c.wMilliseconds是当前时间的毫秒,可以有效避免同一秒内种子相同(但在GetSystemTime(&c)后值是固定的,所以不能反复用),快把你那破srand(time(0))扔了吧!
}

const int length=10,width=10;//地图长宽
const int lnum=10;//雷的数量(要小于length*width)

int mapnum[width][length];//地图,-1雷,0空,1~8周围雷的数量
int mapbool[width][length];//地图状态,-1插旗,0没翻,1翻了
int cmnp[11]={0,9,2,4,1,5,11,0,8,0,4};//字符对应颜色
string mnp[11]={"  ","①","②","③","④","⑤","⑥","⑦","⑧","●","<|"};//字符
int tx=0,ty=0;//光标位置

//游戏核心功能,递归空格与更新
void mapabb(int x,int y){
	if(mapbool[y][x]<1){//状态是-1和0的执行(-1能进来是因为你插错旗了)
		mapbool[y][x]=1;//自动帮你翻了
		setCursorPosition((x+1)*2,y+1);
		if(x==tx&&y==ty)color_print((mapnum[y][x]!=-1)*7,12);
		else color_print(cmnp[mapnum[y][x]+(mapnum[y][x]==-1)*10],7+(mapnum[y][x]==0));
		cout<<mnp[mapnum[y][x]+(mapnum[y][x]==-1)*10];//打印(你看的懂就怪了)
		if(mapnum[y][x]==0)for(int fy=-1;fy<=1;fy++)for(int fx=-1;fx<=1;fx++)if((fx!=0||fy!=0)&&y+fy>=0&&y+fy<width&&x+fx>=0&&x+fx<length)mapabb(x+fx,y+fy);//当前位置是空,就继续打开周围其他格子
	}
}

//获取键盘输入(含方向键)
char game_getch(){
    int inttlttk=getch();//存按下的按键的ASCLL码 
    if(!inttlttk||inttlttk==224){
    	inttlttk=getch();//方向键第一被侦测会返回0或224,所以要再次侦测(我测试了很多遍,都没返回0,可我找到资料写的是会返回0或224,我也不知道) 
	    switch(inttlttk){//72是↑,80是↓,75是←,77是→
	        case 72:return 'w';
	        case 80:return 's';
	        case 75:return 'a';
	        case 77:return 'd';
		}
	}
	return inttlttk+(inttlttk>='A'&&inttlttk<='Z')*' ';//有返回大写的情况,所以加了个判断
}

//判断是否只有非雷的格子全点了
bool mapfo(){
	for(int i=0;i<width;i++)for(int j=0;j<length;j++)if(mapnum[i][j]>=0&&mapbool[i][j]!=1)return false;
	return true;
}

int main(){
	srs();//撒种子
	systemcl(24,12);//设置控制台长和宽
	hidden(false);//隐藏光标
	for(int i=-1;i<=width;i++){
		for(int j=-1;j<=length;j++){
			if(i==-1||j==-1||i==width||j==length)color_print(0,3);
			else if(!i&&!j)color_print(7,12);
			else color_print(0,0);
			cout<<"  ";//mnp[(mapnum[i][j]+(mapnum[i][j]<0)*10)];
		}
		if(i<width)cout<<"\n";
	}
	//打印地图
	char lttk;//定义获取的输入
	
	while(1){//第一次翻开
		lttk=game_getch();//获取的输入
		if(lttk==' ')break;//空格就是翻开了
		setCursorPosition((tx+1)*2,ty+1);
		color_print(cmnp[(mapbool[ty][tx]==1)*mapnum[ty][tx]+(mapbool[ty][tx]==-1)*10],(mapbool[ty][tx]!=0)*(7+(mapbool[ty][tx]==1)*(mapnum[ty][tx]==0)+(mapbool[ty][tx]==-1)*8));
		cout<<mnp[(mapbool[ty][tx]==1)*mapnum[ty][tx]+(mapbool[ty][tx]==-1)*10];//移动前更新
    	tx+=(lttk=='d')*(tx<length-1)-(lttk=='a')*(tx>0);
    	ty+=(lttk=='s')*(ty<width-1)-(lttk=='w')*(ty>0);//移动和限制
		setCursorPosition((tx+1)*2,ty+1);
		color_print(7,12);
		cout<<mnp[(mapbool[ty][tx]==1)*mapnum[ty][tx]+(mapbool[ty][tx]==-1)*10];//移动后更新
	}
	
	axis tmn[width*length-1];//存坐标,不含第一次翻开的位置
	int lentmn=0;//存长度
	for(int i=0;i<width;i++){
		for(int j=0;j<length;j++){
			if(i==ty&&j==tx)continue;
			tmn[lentmn].x=j;
			tmn[lentmn].y=i;
			lentmn++;
		}
	}//存坐标
	for(int i=0;i<lnum;i++){
		int idx=random(0,lentmn-1-i);
		mapnum[tmn[idx].y][tmn[idx].x]=-1;
		swap(tmn[idx],tmn[lentmn-1-i]);
	}//将随机到的几个位置放在地图对应的位置(细节:swap将idx项的值依次丢到了后面,记住,后面要考的)
	for(int i=0;i<width;i++){
		for(int j=0;j<length;j++){
			if(!mapnum[i][j]){
				int shu=0;
				for(int fy=-1;fy<=1;fy++)for(int fx=-1;fx<=1;fx++)shu+=(i+fy>=0&&i+fy<width&&j+fx>=0&&j+fx<length&&mapnum[i+fy][j+fx]==-1);
				mapnum[i][j]=shu;
			}
		}
	}//更新地图
	mapabb(tx,ty);//打印地图
	
	while(1){
		while(1){//重复的地方就不说了
			lttk=game_getch();
			if(lttk==' '&&mapbool[ty][tx]!=-1||lttk=='q'&&mapbool[ty][tx]!=1)break;//可以插/拆旗了
			setCursorPosition((tx+1)*2,ty+1);
			color_print(cmnp[(mapbool[ty][tx]==1)*mapnum[ty][tx]+(mapbool[ty][tx]==-1)*10],(mapbool[ty][tx]!=0)*(7+(mapbool[ty][tx]==1)*(mapnum[ty][tx]==0)+(mapbool[ty][tx]==-1)*8));
			cout<<mnp[(mapbool[ty][tx]==1)*mapnum[ty][tx]+(mapbool[ty][tx]==-1)*10];
        	tx+=(lttk=='d')*(tx<length-1)-(lttk=='a')*(tx>0);
        	ty+=(lttk=='s')*(ty<width-1)-(lttk=='w')*(ty>0);
			setCursorPosition((tx+1)*2,ty+1);
			color_print(7,12);
			cout<<mnp[(mapbool[ty][tx]==1)*mapnum[ty][tx]+(mapbool[ty][tx]==-1)*10];
		}
		if(lttk=='q'){
			mapbool[ty][tx]=0-(!mapbool[ty][tx]);//更新状态
			setCursorPosition((tx+1)*2,ty+1);
			color_print(7,12);
			cout<<mnp[(mapbool[ty][tx]==1)*mapnum[ty][tx]+(mapbool[ty][tx]==-1)*10];//更新
			continue;//进行下一次循环
		}
		//翻格子
		mapabb(tx,ty);//更新状态,打印地图
		if(mapnum[ty][tx]==-1){//还是自信大蛇
			for(int i=0;i<lnum;i++){
				if(tmn[width*length-2-i].x==tx&&tmn[width*length-2-i].y==ty)continue;//光标上的不打印
				setCursorPosition((tmn[width*length-2-i].x+1)*2,tmn[width*length-2-i].y+1);
				color_print(0,7);
				cout<<"●";//打印其他雷的位置
				/*
				《扫雷,但无system("cls")!》帖子扫雷源码真题:
				简答题:tmn[width*length-2-i]的下标意义是什么?为什么可以这样用?
				*/
			}
			break;//没救了
		}
		if(mapfo())break;//可恶,你赢了
	}
	game_getch();//等待返回
	setCursorPosition(0,2+length);
	return 0;
}

按哪个键是标记?
你这还挺好玩的

按Q标记,支持大小写

ok

等会我出个新版,改进成了只有非雷的格子全点了才胜利,你们是逃不掉2选1的 :smiling_imp: