前言

一场为了争夺打印机而开展的战争。

正片

说到打字软件嘛,大家第一个想到的一定是耳熟能详的“金山打字通”。为了让大家能快速的记住我们程序的名字,我们决定起一个简单而又不那么高调的名字————银山打字通!

首先,是程序的设计思路图
1

在实际的开发工作中,我们尽量使用了模块化的编程,将需要用到的功能以函数的形式封装,需要时调用即可。
同时还极大地增加了main函数的可读性

关于程序

美观性

我们为程序设计了清晰明了的菜单界面,调用即可,非常方便。
每个模块中集成了输入检测功能,成功执行则会返回所需功能的数字代号。

以主菜单为例展现设计思路

int Menu()
{
	char mode;
	puts("----------------------------------------------");
	puts("|                 银山打字通                 |");
	puts("|                    v2.0                    |");
	puts("|                vc++6.0特供版               |");
	puts("|               made by VcollX               |");
	puts("|                                            |");
	puts("|                 1.开始游戏                 |");
	puts("|                 2.使用说明                 |");
	puts("|                 3.退出游戏                 |");
	puts("|                                            |");
	puts("|Tip:按下对应编号使用功能                   |"); 
	puts("----------------------------------------------");
	
	mode = getch();
	while(1)
	{
		if('0' < mode && mode < '4') return mode - '0';
		else printf("非法输入,请重试(>__<)\r");
		mode = getch();
	}
} 

趣味性

在各个界面采用了颜文字!!!在枯燥的练习后让自己的身心得到治愈!!!

友好性

银山打字通别名猴子都会用的打字程序
加入了规则界面并严格限制了输入,每个界面中都带有操作提示!
路径的输入直接拖动程序即可,甚至做了不需要直接输入的自动模式用以简化操作!

关于代码

可读性

利用模块化的思路,并在关键地方写明了详细的注释,降低了代码的阅读难度
模块的排版根据调用的先后顺序排版,方便程序员寻找

复杂性

对于菜单的调用都是线性思路并且时间大部分取决于用户的操作时间,所以几乎能忽略不计
重点是游戏实现的主逻辑

这是一个相对完备的逻辑,在循环中每次只读入50个字符进数组中(防止文章太长然后内存爆炸)
进行一次预处理后再按位比较输入与读入的字符,同时完成正确率的计算。
因为也是注重于与用户的交互,所以程序运行时间也可以忽略不计。
硬要讲的话线性阶的时间复杂度是O(n)

while(1)
	{
		int i = 0,j = 0;
		ReadFile(handle,&ch,sizeof(ch),&RealRead,NULL);       //读取文件 
		ErrorID = GetLastError();
		if(ErrorID != 0)                                     //判断是否正常读取 
		{
			printf("文件读取失败(>﹏<),错误码:%hd\n",ErrorID);
			return 0;
		}
		len = strlen(ch);
		if(len == 0) 
		{
			puts("------------------------");
			puts("|退出成功!(= ̄ω ̄=)|");
			puts("------------------------");
			return 2; 
		}
		for(i = 0;i < len;i++)                           //清除文本中的换行符和休止符 
		{
			if(ch[i] == 10 || ch[i] == 13) ch[i] = ' ';
		}
		printf("%s\n",ch);
		for(j = 0;j < len;j++)                            //打字游戏主逻辑 
		{
			read = getch();
			if(read == ch[j]) 
			{
				printf("%c",read);
				AText++;
			}
			else 
			{
				j--;
				printf("\a");
				WText++;
				AText++;
			}
        }
		system("cls");
        if(len != 50) break;
        memset(ch,0,sizeof(ch));                         //记得清空数组不然会出bug 
    }

健壮性

这也是本程序最引以为傲的一点,我们在一切能输入的地方都做了输入检测!
有效防止用户所导致的程序崩溃。

一些例子:

if('0' < mode && mode < '4') return mode - '0';
else printf("非法输入,请重试(>__<)\r");

if(GetFileAttributes(FilePath) != 0) break;
else printf("文件不存在或无法访问,请重试!(>__<)\n");

完成度

完美符合题目要求!!!

技术性

(一)大量运用了winapi
windows api的好处:
1.在出错时能返回错误代码,方便程序维护和查错
2.移植性高,在遇到不同架构或版本的电脑时,只要是windows系统winapi会自动调节适应

(二)开发过程中的新思路和解决方案
1.因为是模块化的编程思路,在程序运行中关闭异常运行的程序要如何解决想了很久
最后决定在main函数中使用一个flag变量标记程序状态,如果异常在完成一次循环时就退出程序

2.为了防止程序内存溢出,运用了程序打开文件的文件指针机制,让文件一部分一部分地读入,有效避免了大文件导致内存爆炸的问题。

3.在自动模式的列表选择界面实现退出功能时又遇到了问题,虽然可以直接靠增加函数模块解决,但是为了增加程序速度与稳定性,我们决定在初始化时就创建一个退出专用文件,让它迅速跑完并返回特定值达到了返回菜单的目的
(一开始用的是空文件,但是后面想了想可以靠存特定哈希的方式增强稳定性,但是已经交了就懒得改了)

创新性

1.超级无敌可爱的颜文字!
2.猴子也会用的自动模式!
3.独具开创性的解决方案!

以下附上源码供学习参考

#include<conio.h>      //getch()
#include<time.h>       //time
#include<stdio.h>      
#include<string.h>     //str相关 
#include<direct.h>     //mkdir()
#include<windows.h>    //win api

WIN32_FIND_DATA Fileinfo[25] = {0};     //使用WIN32API结构体 ,25个表示最多支持25个练习文件 
time_t Start,End;

double AText,WText;
long hfile;//全局文件句柄 

int Menu()
{
	char mode;
	puts("----------------------------------------------");
	puts("|                 银山打字通                 |");
	puts("|                    v2.0                    |");
	puts("|                vc++6.0特供版               |");
	puts("|               made by VcollX               |");
	puts("|                                            |");
	puts("|                 1.开始游戏                 |");
	puts("|                 2.使用说明                 |");
	puts("|                 3.退出游戏                 |");
	puts("|                                            |");
	puts("|Tip:按下对应编号使用功能                   |"); 
	puts("----------------------------------------------");
	
	mode = getch();
	while(1)
	{
		if('0' < mode && mode < '4') return mode - '0';
		else printf("非法输入,请重试(>__<)\r");
		mode = getch();
	}
} 

int NextMenu()
{
	char mode;
	puts("----------------------------------------------");
	puts("|                 银山打字通                 |");
	puts("|                    v2.0                    |");
	puts("|                vc++6.0特供版               |");
	puts("|               made by VcollX               |");
	puts("|                                            |");
	puts("|                 1.自动模式                 |");
	puts("|                 2.手动模式                 |");
	puts("|                 3.返回菜单                 |");
	puts("|                                            |");
	puts("|Tip:按下对应编号使用功能                   |"); 
	puts("----------------------------------------------");
	mode = getch();
	while(1)
	{
		if('0' < mode && mode < '4') return mode - '0';
		else printf("非法输入,请重试(>__<)\r");
		mode = getch();
	}
}

void EdMenu(double rate,int time)
{
	puts("----------------------------------------------");
	puts("|               ***恭喜过关!***              |");
	puts("|                                            |");
  printf("|                正确率: %3.lf%%                |\n",rate);
  printf("|                 速度:%4d秒               |\n",time);
	puts("|                                            |");
	puts("|                                            |");
	puts("|                 o(*≧▽≦)ツ               |");
	puts("|                                            |");
	puts("|                                            |");
	puts("| 按任意键返回菜单                           |"); 
	puts("----------------------------------------------");
	return ;
}

void Rules()
{
	system("cls");
	puts("------------------------------------------------------------------------------");
	puts("| 手动模式:手动输入练习文件路径                                             |");
	puts("| 自动模式:将练习文件放入text文件夹中,程序会列出文件夹内的练习文件,选择编 |");
	puts("| 号以打开文件(第一次打开由程序自动创建text文件夹)                         |");
	puts("| 玩法说明:对照文本输入正确的字母!如果输入错误会发出叫声哦!               |");
	puts("| ps:(暂不支持中文练习)  实在是写不来啦!!!\\(Q A Q )/                      |"); 
	puts("| pss:只能用.txt文件作为练习哦!!!                                         |");
	puts("------------------------------------------------------------------------------");
	puts("\n---按下回车键返回---"); 
	getch();
}

int CreatExitFunc()
{
	long hd;
	char ext[8] = {"exit"},QuitAddr[MAX_PATH];
	GetCurrentDirectory(MAX_PATH,QuitAddr);
	strcat(QuitAddr,"\\texts\\Exit.txt");
	hd = CreateFile(QuitAddr,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
	CloseHandle(hd);
	return 1;
}

int SearchTexts(char* PL) //遍历文件夹下的文件 ,有就打印列表,没有就退出程序并返回总文件数 
{  
	long handle;
	int cont = 1;
	handle = FindFirstFile(PL,&Fileinfo[0]);
	if(handle == INVALID_HANDLE_VALUE)//没有文件就退出 
	{
		printf("未找到可用文件,请将练习文件置于本目录下的texts文件夹中");
		return 0; 
	}
	else//有文件 
	{
		puts("-----------------------------------------------");
		puts("| 请选择需要练习的文件                        |");
		puts("| 如需退出请选择Exit.txt快速返回菜单(= ̄ω ̄=)|");
		puts("-----------------------------------------------");
		printf(" [1] %s\n",Fileinfo[0].cFileName);
	} 
	//继续遍历输出文件列表 
	while(FindNextFile(handle,&Fileinfo[cont])){
		printf(" [%d] %s\n",cont+1,Fileinfo[cont].cFileName);
		cont++;
	}
	FindClose(handle);
	return cont;
}

long FileOpen(char addr[MAX_PATH])  //简单的打开文件函数,打开失败返回0和错误码,打开成功返回文件句柄 
{
	long handle = CreateFile(addr,GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
	 
	if(handle == INVALID_HANDLE_VALUE)
	{
		DWORD ErrorID = GetLastError();
		printf("文件打开失败(>﹏<),错误码:%hd\n",ErrorID);
		return 0;
	}
	return handle;
}

int GameStart(long handle)
{
	char ch[50] = {0},read;
	DWORD RealRead = 0,ErrorID;
	int len;
	while(1)
	{
		int i = 0,j = 0;
		ReadFile(handle,&ch,sizeof(ch),&RealRead,NULL);       //读取文件 
		ErrorID = GetLastError();
		if(ErrorID != 0)                                     //判断是否正常读取 
		{
			printf("文件读取失败(>﹏<),错误码:%hd\n",ErrorID);
			return 0;
		}
		len = strlen(ch);
		if(len == 0) 
		{
			puts("------------------------");
			puts("|退出成功!(= ̄ω ̄=)|");
			puts("------------------------");
			return 2; 
		}
		for(i = 0;i < len;i++)                           //清除文本中的换行符和休止符 
		{
			if(ch[i] == 10 || ch[i] == 13) ch[i] = ' ';
		}
		printf("%s\n",ch);
		for(j = 0;j < len;j++)                            //打字游戏主逻辑 
		{
			read = getch();
			if(read == ch[j]) 
			{
				printf("%c",read);
				AText++;
			}
			else 
			{
				j--;
				printf("\a");
				WText++;
				AText++;
			}	
        }
		system("cls");
        if(len != 50) break;
        memset(ch,0,sizeof(ch));                         //记得清空数组不然会出bug 
    }
    return 1;
}

int Star()  //游戏主逻辑入口函数,正常运行返回1,异常返回0 
{
		int IfError;
	//这俩变量计算正确率 
		WText = 0;
		AText = 0;
		//这段计算时间 
		
		Start = time(NULL);
		
		IfError = GameStart(hfile);              //开始游戏
		if(IfError == 0) return 0; 					//程序异常则退出  
		
		End = time(NULL);
		CloseHandle(hfile);                           //关闭文件句柄 
		
		if(IfError != 2) EdMenu(100-WText/AText*100,End - Start);
		
		getch(); 
		return 1;
}

int main()
{
	char Addr[MAX_PATH],FileAddr[MAX_PATH];	//检测是否存在texts文件夹,若没有则创建一个 
	char* PLADDR;
	GetCurrentDirectory(MAX_PATH,Addr);					//获取当前程序工作目录 
	strcat(Addr,"\\texts");//Addr变为 工作目录\\texts
	if(GetFileAttributes(Addr) == -1) 
		mkdir("texts");
	
	strcat(Addr,"\\*.txt");
	PLADDR = Addr;
	//下面这段设置退出文件列表需要的组件 
	CreatExitFunc();
	//设置命令窗口大小 
	system("mode con cols=113 lines=33");
	//正式开始运行程序 
	while(1)
	{
		int flag = 1;//flag = 1说明游戏进程正常,为0说明有问题,在主函数最后会退出
		int mode,Tnum;
		while(1)
		{
			int a = 0;
			system("cls");
			switch(Menu())
			{
				case 3: return 0;
				case 1: 
					a = 1;
					break;
				case 2: Rules();
			}
			if(a == 1) break;
		}
		system("cls");
		
		mode = NextMenu();
		system("cls");
		if(mode == 1)//自动模式 
		{
			int sum = SearchTexts(PLADDR);
			if(!sum) return 0;                         //寻找可用的练习文档,没找到则退出程序 
					
			printf("------------------------\n总文件数:%d\n",sum);
			printf("输入编号:"); 
			
			scanf("%d",&Tnum);						//输入希望练习的文档编号 
			while(1)
			{
				if(0 < Tnum && Tnum <= sum ) break;
				else printf("非法输入!请重试!(>__<)\n");
				scanf("%d",&Tnum); 
			}
						
			system("cls");
			GetCurrentDirectory(MAX_PATH,FileAddr);         //设定文件路径并准备打开文件 
			strcat(FileAddr,"\\texts\\");
			strcat(FileAddr,Fileinfo[Tnum - 1].cFileName);
			
			hfile = FileOpen(FileAddr);
			if(!hfile) return 0;                   //打开文件失败则退出 
			
			flag = Star();
		}
		else if(mode == 2)//手动模式 
		{
			char FilePath[MAX_PATH];
			printf("请输入文件路径:\n"); 
			scanf("%s",&FilePath);
			while(1)
			{
				if(GetFileAttributes(FilePath) != 0) break;
				else printf("文件不存在或无法访问,请重试!(>__<)\n");
				memset(FilePath,0,sizeof(FilePath));
				scanf("%s",&FilePath);
			}
			system("cls");
			hfile = FileOpen(FilePath);
			if(!hfile) return 0;
			
			flag = Star();
		}
		else if(mode == 3) ;
		
		if(flag = 0) return 0;//如果不能正常开始游戏就退出 
	}
	return 0;
}