C语言实现简单打字游戏
原创的一个打字小游戏,简单好玩,附加代码详细讲解
一、效果图和源码
1.先看运行效果图
2.给出源码。
#include<stdio.h>
#include<time.h>
#include<ctype.h>
#include<windows.h>
#include<conio.h>
#define NUM 16 //字串总数(页面能存在的最大字串数)
struct Block{
char strings[20]; //用于存储字串,20是最大长度
int x; //记录字串的横坐标
int y; //记录字串的纵坐标
int Color[20]; //记录每个字符的颜色
}block[NUM];
enum Difficulty_Level{
Difficulty = 2, Medium = 3, Easy = 4
} speed; //难度等级,字串移动速度,单位秒,由玩家给出
int n = 0; //通过的字串个数
int k = 0; //一个字串输入正确的字符数
double t1, t2; //t用来计时 ,用时间计时方式进行字串下移
void window(int w, int h); //设置窗口大小
void HideCursor(void); //隐藏光标
void color(int c);
void gotoxy(int x, int y);
void StartInterface(void);
void InitInterface(void);
void InitBlock(void);
void Startgame(void);
void JudgeString(int i);
void UpdateBlock(int i);
void MoveBlock(void);
void OtherChoice(int choice); //暂停,重开,结束游戏的选择
void Gameover(void);
int UpdateScore(void);
void window(int w,int h) //设置窗口大小
{
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);//获取标准输出的句柄,命令行的程序会把字符输出到屏幕上。
COORD size = {w, h};//设置窗口的大小。
SetConsoleScreenBufferSize(hOut, size);//重新设置缓冲区大小。
SMALL_RECT rc = {1,1, w, h};//重置窗口位置和大小。
SetConsoleWindowInfo(hOut ,true ,&rc);//重置窗口大小
system("cls");//清理屏幕
return;
}
void HideCursor(void) //隐藏光标
{
CONSOLE_CURSOR_INFO curInfo; //定义光标信息的结构体变量
curInfo.dwSize = 1; //如果没赋值的话,隐藏光标无效
curInfo.bVisible = FALSE; //将光标设置为不可见
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台句柄
SetConsoleCursorInfo(handle, &curInfo); //设置光标信息
}
void color(int c) // 改变字体颜色
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c);
/* 0=黑色 8=灰色
1=蓝色 9=淡蓝色
2=绿色 10=淡绿色
3=湖蓝色 11=淡浅绿色
4=红色 12=淡红色
5=紫色 13=淡紫色
6=黄色 14=淡黄色
7=白色 15=亮白色 */
}
void gotoxy(int x, int y) //设置光标位置
{
COORD xy;
xy.X=x;
xy.Y=y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),xy);
}
void StartInterface(void) //提供难度选择
{
system("cls");
system("color 07");
n = 0; //重置n值,为使再玩一次时n为0
color(7);
gotoxy(30, 12);
printf("欢迎来到 “打 字 游 戏 ”!");
gotoxy(30, 15);
printf("请选择游戏难度:");
gotoxy(30, 18);
printf("1.简单 2.中等 3.困难");
gotoxy(47, 15);
char ch = getchar();
while(ch != '1' && ch != '2' && ch != '3')
{
fflush(stdin);
gotoxy(47, 15);
for(int i = 0; i < 30; i++)
putchar(' ');
gotoxy(47, 15);
color(12);
printf("输入不合法,请重新输入!");
color(7);
Sleep(1000);
gotoxy(47, 15);
for(int i = 0; i < 30; i++)
putchar(' ');
gotoxy(47, 15);
ch = getchar();
}
fflush(stdin);
if(ch == '1')
speed = Easy;
if(ch == '2')
speed = Medium;
if(ch == '3')
speed = Difficulty;
InitInterface(); //初始化游戏界面
}
void InitInterface(void)
{
system("cls");
const int x = 80, y = 30;
for(int i = 0; i < x-1; i++)
{
for(int j = 0; j < y; j++)
{
if(i == 0 || i == x-2 || j == 0 || j == y-1 || i == x-30)
{
gotoxy(i, j);
printf("■");
}
}
}
for(int i = 50; i < 80; i++)
{
gotoxy(i, 13);
printf("■");
}
color(14);
gotoxy(55, 2);
printf("通过个数: %d 个", n);
gotoxy(55, 4);
printf("暂停: 空格");
gotoxy(55, 6);
printf("暂停后任意键继续");
gotoxy(55, 8);
printf("重新开始: Tab");
gotoxy(55, 10);
printf("结束游戏: ESC");
gotoxy(55, 16);
printf("游戏说明:");
gotoxy(59, 18);
printf("在字串下落之前,正");
gotoxy(59, 20);
printf("确输入字串所有字符");
gotoxy(59, 22);
printf("可得一分");
gotoxy(55, 25);
color(4);
if(speed == Easy)
printf("当前难度: 简单");
else if(speed == Medium)
printf("当前难度: 中等");
else
printf("当前难度: 困难");
InitBlock(); //初始化字串
}
void InitBlock(void)
{
srand(unsigned(time(NULL)));
const int size = 5; //初始长度为5
for(int i = 0; i < NUM; i++)
{
for(int j = 0; j < size; j++)
{
block[i].strings[j] = 97 + rand() % (122 - 97 + 1); // a ——z 的ASSCI序列是 97 ——122
block[i].x = 2 + rand() % 43; //x表示字串的初始横坐标
block[i].y = -2*i - 3; //y表示纵坐标
block[i].Color[j] = 7; //将所有字符颜色置为 7(白色)
}
block[i].strings[size] = '\0'; //构成字符串,才能使用strlen()函数
}
Startgame(); //游戏主体函数
}
void Startgame(void) //主体函数 (包含主要算法)
{
t1 = clock(); //开始计时
while(1)
{
int t = 25000 * speed; //经测试25000下运行时间大约为1秒
while(--t)
{
if(kbhit() != 0) //敲击了键盘
break;
} //未敲击键盘时,出循环,t 为 0
if(t != 0) //敲击了键盘,找到要输入的字串,通过首字符确定要输入的字串
{
char ch = getch();
if(ch == 32 || ch == 9 || ch == 27) //暂停,退出,重开
{
OtherChoice(ch);
continue;
}
/*遍历所有的字串,寻找距离底线最近的首字符相符的字串*/
int index[NUM], ans, y_max = 0, j = 0; //前两变量用于记录下标
for(int i = 0; i < NUM; i++)
{
if(0 < block[i].y && block[i].y < 30 && block[i].strings[0] == ch)
{
index[j] = i; //记录字串下标
j++;
}
}
int next = 0;
while(j) //存在符合条件的首字符时,j不为0
{
j--;
next = 1; //运行了该while()则必然找到了符合要求的字串,才能进行下一步操作
if(y_max < block[index[j]].y)
{
ans = index[j]; //记录最优下标
y_max = block[index[j]].y;
}
}
if(next == 1)
{
k = 1; //正确输入了一个字符
/*先输出第一个字符,再对该字串验证后续输入的正确性*/
block[ans].Color[0] = 10;
color(block[ans].Color[0]);
gotoxy(block[ans].x, block[ans].y);
putchar(block[ans].strings[0]);
JudgeString(ans); //判断后续字串输入的正确性
}
fflush(stdin); // 敲错后刷新缓冲区,防止乱敲键盘,导致程序一直运行上述代码
}
t2 = clock();
if((t2 - t1) / CLOCKS_PER_SEC > speed) //时间间隔超过 SPEED 秒,字串下移
MoveBlock();
}
}
void JudgeString(int i) //主体函数 (包含主要算法)
{
while(1)
{
t2 = clock();
if((t2 - t1) / CLOCKS_PER_SEC > speed) //超时,字串下移
MoveBlock();
int t = 25000 * speed;
while(--t)
{
if(kbhit() != 0) //敲击了键盘
break;
} //未敲击键盘时,出循环后,t 为 0
if(t != 0) //以下为敲击键盘的情况,未敲击时直接继续死循环
{
char ch = getch();
if(ch == 32 || ch == 9 || ch == 27) //暂停,退出,重开
{
OtherChoice(ch);
continue;
}
if(0 < block[i].y && block[i].y < 30 && block[i].strings[k] == ch) //下一个字符输入正确
{
block[i].Color[k] = 10;
color(block[i].Color[k]);
gotoxy(block[i].x + k, block[i].y);
putchar(block[i].strings[k]);
k++;
}
else //输入错误,则该字串重置, 即将整个字串的颜色改为白色
{
for(int j = 0; j < k; j++)
{
block[i].Color[j] = 7;
color(block[i].Color[j]);
gotoxy(block[i].x + j, block[i].y);
putchar(block[i].strings[j]);
}
k = 0; //k也重置, 即正确的字符为0个
break;
}
int size = strlen(block[i].strings);
if(k == size) //完全正确,用空格消除字串
{
putchar('\a'); //蜂鸣
gotoxy(block[i].x, block[i].y);
while(k > 0)
{
putchar(' ');
k--; //退出循环后,k恢复到 0
}
n++; //正确数加一
color(15);
gotoxy(65, 2);
printf("%d", n);
UpdateBlock(i); //更新该字串
break;
}
}
}
}
void MoveBlock(void) //字串下移
{
/*先删除原位置,再进行下移*/
for(int i = 0; i < NUM; i++)
{
int size = strlen(block[i].strings);
if(block[i].y > 0)
/* 用空格替代原字串,以实现下次循环时字串的下移 */
{
gotoxy(block[i].x, block[i].y);
while(size > 0)
{
putchar(' ');
size--;
}
}
block[i].y += 2; //下移
}
for(int i = 0; i < NUM; i++)
{
if(block[i].y > 30) //字串超过底线,游戏结束
Gameover();
if(block[i].y > 0) //字串可以输出
{
int size = strlen(block[i].strings);
for(int j = 0; j < size; j++)
{
gotoxy(block[i].x + j, block[i].y);
color(block[i].Color[j]);
putchar(block[i].strings[j]);
}
}
}
t1 = clock(); //重新计时
}
void UpdateBlock(int i) //更新字串
{
int size = 5 + n / 7; //难度系数,每对7个,单个字串的字符数加一
int last_y = 0; //寻找排在最后的字串
for(int order = 0; order < NUM; order++)
last_y = last_y < block[order].y ? last_y : block[order].y;
for(int j = 0; j < size; j++)
{
block[i].Color[j] = 7; //初始颜色为白色
block[i].strings[j] = 97 + rand() % (122 - 97 + 1); // a ——z 的ASSCI序列是 97 ——122
block[i].x = 2 + rand() % (48 - size); //x表示字串的初始横坐标
block[i].y = last_y - 2; //将更新后的字串排在最后
}
block[i].strings[size] = '\0'; //构成字符串,才能使用strlen()函数
}
void OtherChoice(int choice)
{
switch(choice)
{
case 32: //暂停
system("pause>nul"); //暂停
t1 = clock(); //暂停结束后重新计时
break;
case 27: //退出
Gameover();
break;
default: //重新开始
StartInterface();
}
}
void Gameover(void)
{
if(kbhit())
fflush(stdin);
system("cls");
gotoxy(28, 14);
color(3);
printf("游 戏 结 束 !");
Sleep(1200);
int max = UpdateScore(); //记录最高分,写到文件中,并返回最终的最高分
system("cls");
system("color 7C");
int i = 0;
/* 绘制爱心 */
for (float y = 1.3f; y > -1.3f; y -= 0.1f, i++)
{
gotoxy(7, i);
for (float x = -1.5f; x < 1.5f; x += 0.05f)
{
float a = x * x + y * y - 1;
putchar(a * a * a - x * x * y * y * y <= 0.0f ? '*' : ' ');
}
putchar('\n');
}
gotoxy(27, 24);
printf(" 得 分 : %d ", n);
gotoxy(27, 26);
printf(" 最 高 分 : %d ", max);
double a1, a2; //计时
a1 = clock();
while(1) //使爱心频闪
{
a2 = clock();
if((a2 - a1) / CLOCKS_PER_SEC > 1.8) //频闪1.8秒后
{
gotoxy(29, 28);
printf("按任意键继续");
}
Sleep(100);
system("color 7E");
Sleep(100);
system("color 7B");
Sleep(100);
system("color 79");
Sleep(100);
system("color 7D");
Sleep(100);
system("color 74");
Sleep(100);
system("color 76");
Sleep(100);
system("color 71");
Sleep(100);
system("color 72");
Sleep(100);
system("color 74");
if(kbhit())
{
char ch = getch();
break;
}
}
gotoxy(29, 28);
printf("再来一次? Y / N : ");
char ch = getchar();
ch = tolower(ch);
while(ch != 'y' && ch != 'n')
{
fflush(stdin);
gotoxy(49, 28);
for(int i = 0; i < 30; i++)
putchar(' ');
gotoxy(49,28);
printf("输入不合法,请重新输入!");
Sleep(1000);
gotoxy(49, 28);
for(int i = 0; i < 30; i++)
putchar(' ');
gotoxy(49, 28);
ch = getchar();
ch = tolower(ch);
}
fflush(stdin);
if(ch == 'y')
StartInterface();
if(ch == 'n')
exit(1);
}
int UpdateScore(void) //记录最高分,写到文件中,并返回最终的最高分
{
int score[3];
FILE *fp = fopen("打字游戏最高分记录.txt", "r"); //只读
if(fp == NULL) //创建新文件并初始化成绩
{
fp = fopen("打字游戏最高分记录.txt", "w+"); //创建文件,可读写,但会清除原文件
for(int i = 0; i < 3; i++)
score[i] = 0;
}
else
for(int i = 0; i < 3; i++)
fscanf(fp, "%d", &score[i]);
fclose(fp);
fp = fopen("打字游戏最高分记录.txt", "r+"); //读写
if(speed == Easy) //简单
{
score[0] = score[0] > n ? score[0] : n;
for(int i = 0; i < 3; i++)
fprintf(fp, "%d ", score[i]);
fputs("\n上面分别为三种难度系数的最高分,不要乱改!!!,若修改后导致程序运行错误\n\
或者最高分为乱码数字,将此.txt文件永久删除即可。", fp);
fclose(fp);
return score[0];
}
if(speed == Medium) //中等
{
score[1] = score[1] > n ? score[1] : n;
for(int i = 0; i < 3; i++)
fprintf(fp, "%d ", score[i]);
fputs("\n上面分别为三个难度系数的最高分,不要乱改!!!", fp);
fclose(fp);
return score[1];
}
if(speed == Difficulty) //困难
{
score[2] = score[2] > n ? score[2] : n;
for(int i = 0; i < 3; i++)
fprintf(fp, "%d ", score[i]);
fputs("\n上面分别为三个难度系数的最高分,不要乱改!!!", fp);
fclose(fp);
return score[2];
}
}
int main(void)
{
window(80, 30); //设置窗口尺寸,意思是设置一个x = 80, y = 30尺寸的cmd窗口
HideCursor(); //隐藏光标
StartInterface(); //询问游戏难度环节
return 0;
}
二、核心算法
由运行效果图可以看出,算法核心在于字串的下落、玩家输入正确性和字串之间的交互,以及两者之间的结合。
1.字串的下落可以用过计时控制,字串下落前,使用t1 = clock()开始计时,完成一些操作后,使用t2 = clock(), if( (t2-t1) / CLOCKS_PER_SEC > t), 字串下落。意思是如果运行这一些操作后,时间间隔大于t秒,就让字串下落。
2.玩家输入的正确性判断时,读取玩家输入的第一个字符,寻找在游戏窗口中第一个字符符合且距离底线最近的字串,然后单独把这个字串拿出来,判断玩家后续输入的正确性。
3.字串下落和玩家输入的结合则可以用kbhit()函数和getch()函数配合使用,前者判断是否敲击了键盘,后者直接读取敲击的字符而无需按回车键。算法实现:如果玩家敲击了键盘,就读取输入然后寻找匹配的字串,并验证输入的正确性,期间不断计时,根据时间间隔下移字串。
三、代码结构讲解
1.头文件,宏,全局变量
#include<stdio.h>
#include<time.h>
#include<ctype.h>
#include<windows.h>
#include<conio.h>
#define NUM 16 //字串总数(页面能存在的最大字串数)
struct Block{
char strings[20]; //用于存储字串,20是最大长度
int x; //记录字串的横坐标
int y; //记录字串的纵坐标
int Color[20]; //记录每个字符的颜色
}block[NUM];
enum Difficulty_Level{
Difficulty = 2, Medium = 3, Easy = 4
} speed; //难度等级,字串移动速度,单位秒,由玩家给出
int n = 0; //通过的字串个数
int k = 0; //一个字串输入正确的字符数
double t1, t2; //t用来计时 ,用时间计时方式进行字串下移
(1)头文件
1.<time.h> 中使用了time()来获取随机数,clock()用于计时;
2.<ctype.h> 中使用了tolower(), 将大写字母变成小写;
3.<windows.h> 中使用了控制台句柄来实现改变光标位置和改变颜色,即color()和gotoxy()函数,同时还使用了system()函数中的清屏、改变颜色(前景加背景)、屏幕冻结等功能,使用Sleep()来实现睡眠;
4.<conio.h> 主要用到了getch(),该函数的作用是不按回车直接读取键盘输入,kbhit()用于判断是否敲击键盘。
(2)宏
全局一个宏NUM,表示字串的数量。给定数值16的原因是游戏窗口高度为30,每个字串的间隔为1,为了让字串占满窗口,设置为16。
(3)全局变量
结构体block代表一个字串,总共16个;
枚举speed表示三种难度;
int n表示输入正确的字串数;
int k表示单个字串中,输入正确的字符数;所以 n 和 k 需要一开始就置位0;
double t1, t2 用于计时,此处不能用int类型,因为返回的时间是带小数点的。
2.辅助功能函数:
这四个函数只起辅助作用,会贯穿整个代码,但是其本身没有什么算法。所以后续不会做详细介绍
void window(int w, int h); //设置窗口大小
void HideCursor(void); //隐藏光标
void color(int c); //改变字体颜色
void gotoxy(int x, int y); //设置光标位置
3.main()函数:
main()函数很简单,在整个代码的最下方。
功能: 设置一个窗口,隐藏光标,和打开整个游戏的入口函数,即StartInterface()函数。
int main(void)
{
window(80, 30); //设置窗口尺寸,意思是设置一个x = 80, y = 30尺寸的cmd窗口
HideCursor(); //隐藏光标
StartInterface(); //询问游戏难度环节
return 0;
}
4.void StartInterface(void) 询问游戏难度
使用fflush()(清除缓冲区)优化输入,防止输入错误,和缓冲区残留字符干扰游戏,最后通过InitInitface(),进行游戏界面初始化。初始化比较简单就不单独介绍,直接跳过了。
void StartInterface(void) //提供难度选择
{
system("cls"); //清屏
system("color 07"); //黑底白字
n = 0; //重置n值,为使再玩一次时n为0
color(7);
gotoxy(30, 12);
printf("欢迎来到 “打 字 游 戏 ”!");
gotoxy(30, 15);
printf("请选择游戏难度:");
gotoxy(30, 18);
printf("1.简单 2.中等 3.困难");
gotoxy(47, 15);
char ch = getchar();
while(ch != '1' && ch != '2' && ch != '3')
{
fflush(stdin);
gotoxy(47, 15);
for(int i = 0; i < 30; i++)
putchar(' ');
gotoxy(47, 15);
color(12);
printf("输入不合法,请重新输入!");
color(7);
Sleep(1000);
gotoxy(47, 15);
for(int i = 0; i < 30; i++)
putchar(' ');
gotoxy(47, 15);
ch = getchar();
}
fflush(stdin);
if(ch == '1')
speed = Easy;
if(ch == '2')
speed = Medium;
if(ch == '3')
speed = Difficulty;
InitInterface(); //初始化游戏界面
}
5.void InitBlock(void)初始化字串
使用srand()用时间作为种子,再通过rand()获取随机数,字串的横坐标为随机数,纵坐标出初始为负数,是因为窗口的纵坐标范围是0——30,并让字串的纵坐标依次排列,最后再初始化字串每个字符的颜色。
注意字串必须构成字符串,因为后续需要使用到strlen()函数。
void InitBlock(void)
{
srand(unsigned(time(NULL)));
const int size = 5; //初始长度为5
for(int i = 0; i < NUM; i++)
{
for(int j = 0; j < size; j++)
{
block[i].strings[j] = 97 + rand() % (122 - 97 + 1); // a ——z 的ASSCI序列是 97 ——122
block[i].x = 2 + rand() % 43; //x表示字串的初始横坐标
block[i].y = -2*i - 3; //y表示纵坐标
block[i].Color[j] = 7; //将所有字符颜色置为 7(白色)
}
block[i].strings[size] = '\0'; //构成字符串,才能使用strlen()函数
}
Startgame(); //游戏主体函数
}
6. void Startgame(void) 游戏主体函数
1.总体思想:若玩家开始输入,捕获输入内容的第一个字符,然后去寻找第一个字符匹配的字串,若找到了,将这个字串单独拿出来,进行后续输入的判断,没有找到,则什么也不做,并不断重复这些操作直到游戏结束。
2.需要克服的问题: (1)玩家输入不需要按回车结束输入; (2)字串必须定时下落; (3) 玩家输入不能影响字串的下落。
3.算法实现:clock()函数返回时间; kbhit()函数当敲击键盘时返回非零值; getch()函数直接读取输入不需要回车键;
4.细节优化: 寻找字串时,需优先寻找距离下限最近的,因为当窗口中有两个首字符一样的字串时,玩家更期望先消除更下面的那个。
int t = 25000 * speed; //经测试25000下运行时间大约为1秒
while(--t)
{
if(kbhit() != 0) //敲击了键盘
break;
}
使用while(--t)判断 t 遍是否有键盘输入,原因:假设只判断一遍,会立即返回0值(没有敲击键盘)。令t = 25000 * speed, 可以使当没有任何输入时,while(--t)大约运行 speed 秒, 就可以保证没有输入时,每经过 speed 秒字串下移。
void Startgame(void) //主体函数 (包含主要算法)
{
t1 = clock(); //开始计时
while(1)
{
int t = 25000 * speed; //经测试25000下运行时间大约为1秒
while(--t)
{
if(kbhit() != 0) //敲击了键盘
break;
} //未敲击键盘时,出循环,t 为 0
if(t != 0) //敲击了键盘,找到要输入的字串,通过首字符确定要输入的字串
{
char ch = getch();
if(ch == 32 || ch == 9 || ch == 27) //暂停,退出,重开
{
OtherChoice(ch);
continue;
}
/*遍历所有的字串,寻找距离底线最近的首字符相符的字串*/
int index[NUM], ans, y_max = 0, j = 0; //前两变量用于记录下标
for(int i = 0; i < NUM; i++)
{
if(0 < block[i].y && block[i].y < 30 && block[i].strings[0] == ch)
{
index[j] = i; //记录字串下标
j++;
}
}
int next = 0;
while(j) //存在符合条件的首字符时,j不为0
{
j--;
next = 1; //运行了该while()则必然找到了符合要求的字串,才能进行下一步操作
if(y_max < block[index[j]].y)
{
ans = index[j]; //记录最优下标
y_max = block[index[j]].y;
}
}
if(next == 1)
{
k = 1; //正确输入了一个字符
/*先输出第一个字符,再对该字串验证后续输入的正确性*/
block[ans].Color[0] = 10;
color(block[ans].Color[0]);
gotoxy(block[ans].x, block[ans].y);
putchar(block[ans].strings[0]);
JudgeString(ans); //判断后续字串输入的正确性
}
fflush(stdin); // 敲错后刷新缓冲区,防止乱敲键盘,导致程序一直运行上述代码
}
t2 = clock();
if((t2 - t1) / CLOCKS_PER_SEC > speed) //时间间隔超过 SPEED 秒,字串下移
MoveBlock();
}
}
7. void JudgeString(int i) 第二个主体函数
1.总体思想:在上一个函数中找到了第一个首字符匹配的字串,这个函数则将这个字串单独拿出来,继续判断后续输入的正确性。若后续输入完全正确,就消除该字串(空格覆盖),若出现错误,需要从第一个字符重新输入(从头重新输入),或者选择消除其他的字串。
2.算法实现:给出死循环,获取玩家输入并做出判断,全对或者出现错误,将字串颜色恢复为白色并退出死循环返回上一层函数。函数声明中的 int i 表示第 i 个字串。
void JudgeString(int i) //主体函数 (包含主要算法)
{
while(1)
{
t2 = clock();
if((t2 - t1) / CLOCKS_PER_SEC > speed) //超时,字串下移
MoveBlock();
int t = 25000 * speed;
while(--t)
{
if(kbhit() != 0) //敲击了键盘
break;
} //未敲击键盘时,出循环后,t 为 0
if(t != 0) //以下为敲击键盘的情况,未敲击时直接继续死循环
{
char ch = getch();
if(ch == 32 || ch == 9 || ch == 27) //暂停,退出,重开
{
OtherChoice(ch);
continue;
}
if(0 < block[i].y && block[i].y < 30 && block[i].strings[k] == ch) //下一个字符输入正确
{
block[i].Color[k] = 10;
color(block[i].Color[k]);
gotoxy(block[i].x + k, block[i].y);
putchar(block[i].strings[k]);
k++;
}
else //输入错误,则该字串重置, 即将整个字串的颜色改为白色
{
for(int j = 0; j < k; j++)
{
block[i].Color[j] = 7;
color(block[i].Color[j]);
gotoxy(block[i].x + j, block[i].y);
putchar(block[i].strings[j]);
}
k = 0; //k也重置, 即正确的字符为0个
break;
}
int size = strlen(block[i].strings);
if(k == size) //完全正确,用空格消除字串
{
putchar('\a'); //蜂鸣
gotoxy(block[i].x, block[i].y);
while(k > 0)
{
putchar(' ');
k--; //退出循环后,k恢复到 0
}
n++; //正确数加一
color(15);
gotoxy(65, 2);
printf("%d", n);
UpdateBlock(i); //更新该字串
break;
}
}
}
}
8.void MoveBlock(void) 字串下移 和 void UpdateBlock(int i)更新字串
1.字串下移采用先删除原字串,再在新位置输出字串。删除使用空格覆盖,新位置则在空格覆盖完后,所有字串y轴 + 2的方式。每次移动完一次字串重新计时。
2.更新字串时先遍历一边所有字串,找到排在最末尾的那个字串,然后更新的字串就放在最末尾的后面,即last_y - 2,其他跟初始化字串一样。
void MoveBlock(void) //字串下移
{
/*先删除原位置,再进行下移*/
for(int i = 0; i < NUM; i++)
{
int size = strlen(block[i].strings);
if(block[i].y > 0)
/* 用空格替代原字串,以实现下次循环时字串的下移 */
{
gotoxy(block[i].x, block[i].y);
while(size > 0)
{
putchar(' ');
size--;
}
}
block[i].y += 2; //下移
}
for(int i = 0; i < NUM; i++)
{
if(block[i].y > 30) //字串超过底线,游戏结束
Gameover();
if(block[i].y > 0) //字串可以输出
{
int size = strlen(block[i].strings);
for(int j = 0; j < size; j++)
{
gotoxy(block[i].x + j, block[i].y);
color(block[i].Color[j]);
putchar(block[i].strings[j]);
}
}
}
t1 = clock(); //重新计时
}
void UpdateBlock(int i)
{
int size = 5 + n / 7; //难度系数,每对7个,单个字串的字符数加一
int last_y = 0; //寻找排在最后的字串
for(int order = 0; order < NUM; order++)
last_y = last_y < block[order].y ? last_y : block[order].y;
for(int j = 0; j < size; j++)
{
block[i].Color[j] = 7; //初始颜色为白色
block[i].strings[j] = 97 + rand() % (122 - 97 + 1); // a ——z 的ASSCI序列是 97 ——122
block[i].x = 2 + rand() % (48 - size); //x表示字串的初始横坐标
block[i].y = last_y - 2; //将更新后的字串排在最后
}
block[i].strings[size] = '\0'; //构成字符串,才能使用strlen()函数
}
9. void OtherChoice(int choice) 其他选择(暂停、结束游戏、重新开始)
代码中 sysytem("pause") ;的效果如下:
而 system("pause>nul") 可以消除 “请按任意键继续. . .” 这几个字。
void OtherChoice(int choice)
{
switch(choice)
{
case 32: //暂停
system("pause>nul"); //暂停
t1 = clock(); //暂停结束后重新计时
break;
case 27: //退出
Gameover();
break;
default: //重新开始
StartInterface();
}
}
10. void Gameover(void) 游戏结束
游戏结束后需要更新一下最高分记录,然后可以整一些花里胡哨的东西来美化一下界面。在下面代码中使用了绘制爱心并使爱心频闪的方式。
右边是爱心的数学函数公式,其算法实现是构造一个矩形,从上至下,从左至右,在该数学函数公式范围内的输出 ‘ * ’, 否则输出空格。
让爱心频闪的算法是,使用 Sleep(100), 每睡眠100 ms,更换一次字体颜色。
void Gameover(void)
{
if(kbhit())
fflush(stdin);
system("cls");
gotoxy(28, 14);
color(3);
printf("游 戏 结 束 !");
Sleep(1200);
int max = UpdateScore(); //记录最高分,写到文件中,并返回最终的最高分
system("cls");
system("color 7C");
int i = 0;
/* 绘制爱心 */
for (float y = 1.3f; y > -1.3f; y -= 0.1f, i++)
{
gotoxy(7, i);
for (float x = -1.5f; x < 1.5f; x += 0.05f)
{
float a = x * x + y * y - 1;
putchar(a * a * a - x * x * y * y * y <= 0.0f ? '*' : ' ');
}
putchar('\n');
}
gotoxy(27, 24);
printf(" 得 分 : %d ", n);
gotoxy(27, 26);
printf(" 最 高 分 : %d ", max);
double a1, a2; //计时
a1 = clock();
while(1) //使爱心频闪
{
a2 = clock();
if((a2 - a1) / CLOCKS_PER_SEC > 1.8) //频闪1.8秒后
{
gotoxy(29, 28);
printf("按任意键继续");
}
Sleep(100);
system("color 7E");
Sleep(100);
system("color 7B");
Sleep(100);
system("color 79");
Sleep(100);
system("color 7D");
Sleep(100);
system("color 74");
Sleep(100);
system("color 76");
Sleep(100);
system("color 71");
Sleep(100);
system("color 72");
Sleep(100);
system("color 74");
if(kbhit())
{
char ch = getch();
break;
}
}
gotoxy(29, 28);
printf("再来一次? Y / N : ");
char ch = getchar();
ch = tolower(ch);
while(ch != 'y' && ch != 'n')
{
fflush(stdin);
gotoxy(49, 28);
for(int i = 0; i < 30; i++)
putchar(' ');
gotoxy(49,28);
printf("输入不合法,请重新输入!");
Sleep(1000);
gotoxy(49, 28);
for(int i = 0; i < 30; i++)
putchar(' ');
gotoxy(49, 28);
ch = getchar();
ch = tolower(ch);
}
fflush(stdin);
if(ch == 'y')
StartInterface();
if(ch == 'n')
exit(1);
}
11. int UpdateScore(void) 记录最高分,写到文件中,并返回最终的最高分
此函数主要用到了打开文件的一些方法,下面罗列了打开文件的各种方式,就不多加赘述了
int UpdateScore(void) //记录最高分,写到文件中,并返回最终的最高分
{
int score[3];
FILE *fp = fopen("打字游戏最高分记录.txt", "r"); //只读
if(fp == NULL) //创建新文件并初始化成绩
{
fp = fopen("打字游戏最高分记录.txt", "w+"); //创建文件,可读写,但会清除原文件
for(int i = 0; i < 3; i++)
score[i] = 0;
}
else
for(int i = 0; i < 3; i++)
fscanf(fp, "%d", &score[i]);
fclose(fp);
fp = fopen("打字游戏最高分记录.txt", "r+"); //读写
if(speed == Easy) //简单
{
score[0] = score[0] > n ? score[0] : n;
for(int i = 0; i < 3; i++)
fprintf(fp, "%d ", score[i]);
fputs("\n上面分别为三种难度系数的最高分,不要乱改!!!,若修改后导致程序运行错误\n\
或者最高分为乱码数字,将此.txt文件永久删除即可。", fp);
fclose(fp);
return score[0];
}
if(speed == Medium) //中等
{
score[1] = score[1] > n ? score[1] : n;
for(int i = 0; i < 3; i++)
fprintf(fp, "%d ", score[i]);
fputs("\n上面分别为三个难度系数的最高分,不要乱改!!!", fp);
fclose(fp);
return score[1];
}
if(speed == Difficulty) //困难
{
score[2] = score[2] > n ? score[2] : n;
for(int i = 0; i < 3; i++)
fprintf(fp, "%d ", score[i]);
fputs("\n上面分别为三个难度系数的最高分,不要乱改!!!", fp);
fclose(fp);
return score[2];
}
}
更多推荐
所有评论(0)