纯C语言打字游戏

本文最后更新于:2019年10月12日 下午

纯C语言打字游戏

最基础的是一个单线程的版本,刚学会C语言是可以看懂的,也可以自己写出来,但是游戏体验不好。

后续增加的多线程版本,优化了游戏体验,也会添加更多的游戏模式,让这个小游戏变得不那么枯燥。

单线程版本的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
/*************************************
游戏名称: 打字游戏
单线程,玩的过程中会卡顿
但是已经是一个完整的游戏了,适合初学者入门
*************************************/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <windows.h>
#include <conio.h>
#include <string.h>
#include <ctype.h>

#define LETTER_COUNT 30 //总共多少个单词下落
#define SPEED 500 //单词下落的速度
#define BULLET_SPEED 30 //子弹射击的速度

/***************初始化***************/
//设计字母、子弹
typedef struct tag_letter
{
//位置
int x, y;
//字母
char ch;
//是否显示 0-不显示,1-显示
int isDisplay;
//生命 0-死亡,1-生存
int life;
}Letter;

Letter letters[LETTER_COUNT];
Letter bullet;
//游戏字母初始化
void initLetters()
{
srand((unsigned)time(NULL));
for(int i = 0; i < LETTER_COUNT; i++)
{
letters[i].ch = rand() % 26 + 'A';
letters[i].x = rand() % 80;
letters[i].y = - i * 2;
letters[i].life = 1;
}
}
//子弹初始化
void initBullet(int x)
{
bullet.ch = 12;
bullet.x = x;
bullet.y = 25;
bullet.life = 1;
}

/***************功能函数***************/
//定位到屏幕的某个位置
void gotoxy(int x, int y)
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
COORD coord;
coord.X = x;
coord.Y = y;
SetConsoleCursorPosition(handle, coord);
}

//绘制字母
void drawLetter(char ch, int x, int y)
{
gotoxy(x, y);
printf("%c", ch);
}

//用来判断所有字母是否应该显示
void judgeLetterDisplay()
{
for(int i = 0; i < LETTER_COUNT; i++)
{
if(letters[i].life == 0)
{
letters[i].isDisplay = 0;
}
else
{
if(letters[i].y < 0 || letters[i].y > 24)
{
letters[i].isDisplay = 0;
}
else
{
letters[i].isDisplay = 1;
}
}
}
}

//letterMoving:让所有字母下降
void letterMoving()
{
judgeLetterDisplay();
for(int i = 0; i < LETTER_COUNT; i++)
{
if(letters[i].y < 25)
{
if(letters[i].isDisplay == 1)
{
drawLetter(' ', letters[i].x, letters[i].y);
drawLetter(letters[i].ch, letters[i].x, letters[i].y + 1);
}
else
{
drawLetter(' ', letters[i].x, letters[i].y);
}
letters[i].y++;
}
else
{
drawLetter(' ', letters[i].x, letters[i].y);
}
}
}
//子弹飞行
void bulletMoving()
{
if(bullet.life == 1)
{
drawLetter(' ', bullet.x, bullet.y);
drawLetter(bullet.ch, bullet.x, bullet.y - 1);
}
bullet.y--;
}
//计算答案
int calcResult()
{
int counter = 0;
for(int i = 0; i < LETTER_COUNT; i++)
{
if(letters[i].life == 0)
{
counter++;
}
}
return counter;
}
//隐藏控制台光标
void hideCursor()
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(handle, &CursorInfo);//获取控制台光标信息
CursorInfo.bVisible = 0; //隐藏控制台光标
SetConsoleCursorInfo(handle, &CursorInfo);//设置控制台光标状态
}

//提示信息
void menu()
{
printf("一个迷你的打字游戏\n\n把输入法调成英文状态就可以开始玩了\n\n");
printf("%d个字母即将来袭!!!\n\n", LETTER_COUNT);
system("pause");
system("cls");
}


int main()
{
hideCursor();
menu();
initLetters();
while(letters[LETTER_COUNT - 1].y < 25)
{
bullet.life = 1;
if(_kbhit())//获取键盘按键
{
char ch = _getch();
ch = toupper(ch);
for(int k = 0; k < LETTER_COUNT; k++)//遍历每个子弹,看看有没有对应按键的字母
{
if(letters[k].isDisplay == 1 && letters[k].ch == ch)
{
initBullet(letters[k].x);
while(bullet.y > 0)
{
bulletMoving();
if(bullet.y == letters[k].y)
{
letters[k].life = 0;
bullet.life = 0;
drawLetter(' ', bullet.x, bullet.y);
}
Sleep(BULLET_SPEED);
}
break;
}
}
}
Sleep(SPEED);
letterMoving();
}
/*******************游戏结束******************/
gotoxy(30, 12);
printf("总共有%d个字母,你打掉了%d个",LETTER_COUNT, calcResult());
getchar();
return 0;
}

多线程版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <process.h>
#include <time.h>
#include <ctype.h>
#include <conio.h>

//字母结构体
typedef struct tag_letter
{
int x, y; //字母下落位置
char ch; //字母
int life; //字母是否存活
}Letter;

/********************全局变量区************************/
int letterCount = 100; //下落字母的总数量
int beginFlag = 1; //游戏何时结束,0-表示结束
int downCount; //下落字母数量统计
int dropLetter; //打掉字母数量统计
int errorCount; //按键错误统计
int speed = 500; //下落速度
int bulletSpeed = 30; //子弹速度
//多线程-线程锁
CRITICAL_SECTION csCursor;//光标锁


Letter *letters; //创建字母序列
int *vis; //标记字母是否出现过
int *visBullet; //标记对应字母是否有子弹正在发射
int *bulletflag; //子弹编号

//初始化全局变量,和开辟对应数组空间
void init()
{
downCount = 0;
dropLetter = 0;
errorCount = 0;
vis = (int *)malloc(sizeof(int) * letterCount);
visBullet = (int *)malloc(sizeof(int) * letterCount);
bulletflag = (int *)malloc(sizeof(int) * letterCount);
memset(vis, 0, sizeof(int) * letterCount);
memset(visBullet, 0, sizeof(int) * letterCount);
for(int i = 0; i < letterCount; i++)
{
bulletflag[i] = i;
}
}
//初始化字母序列
void initLetters()
{
letters = (struct tag_letter *)malloc(sizeof(struct tag_letter) * letterCount);
srand((unsigned)time(NULL));
for(int i = 0; i < letterCount; i++)
{
letters[i].ch = rand() % 26 + 'A';
letters[i].x = rand() % 80;
letters[i].y = - i * 2;
letters[i].life = 1;
}
}

/***************功能函数***************/
//定位到屏幕的某个位置
void gotoxy(int x, int y)
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
COORD coord;
coord.X = x;
coord.Y = y;
SetConsoleCursorPosition(handle, coord);
}

//绘制字母
void drawLetter(char ch, int x, int y)
{
EnterCriticalSection(&csCursor);
gotoxy(x, y);
printf("%c", ch);
LeaveCriticalSection(&csCursor);
}
//设置窗口标题
void setTitle(void *p)
{
while(1)
{
char str[30] = "";
sprintf(str, "title 总字母:%d 下落字母:%d 击落:%d", letterCount, downCount, dropLetter);
system(str);
}
}
DWORD WINAPI runBullet(void *p)
{
int letterId = *(int *) p;
if(!visBullet[letterId])
{
visBullet[letterId] = 1;
}
int y = 25;
int x = letters[letterId].x;
while(y > letters[letterId].y)
{

drawLetter(' ', x, y);
drawLetter(12, x, y - 1);

Sleep(bulletSpeed);
y--;
}
drawLetter(' ', x, y);
letters[letterId].life = 0;
drawLetter(' ', letters[letterId].x, letters[letterId].y);
}
//letterMoving:让所有字母下降一个位置
void letterMoving()
{
int cnt = 0;
for(int i = 0; i < letterCount; i++)
{
if(letters[i].life == 1)
{
cnt++;
}
if(letters[i].y < 25 && letters[i].y >= 0 && letters[i].life == 1)
{
if(!vis[i])
{
downCount++;
vis[i] = 1;
}
drawLetter(' ', letters[i].x, letters[i].y);
drawLetter(letters[i].ch, letters[i].x, letters[i].y + 1);
}
else if(letters[i].y > 25)
{
letters[i].life = 0;
}
else if(letters[i].y == 25)
{
drawLetter(' ', letters[i].x, letters[i].y);
}
letters[i].y++;
}
if(cnt == 0)
{
beginFlag = 0;
}
}
//字母不断下降
DWORD WINAPI runLetter(void *p)
{
while(letters[letterCount - 1].y < 25)
{
letterMoving();
Sleep(speed);
}
beginFlag = 0;
}

//隐藏控制台光标
void hideCursor()
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(handle, &CursorInfo);//获取控制台光标信息
CursorInfo.bVisible = 0; //隐藏控制台光标
SetConsoleCursorInfo(handle, &CursorInfo);//设置控制台光标状态
}
//开始游戏提示
void gameBegin()
{
printf("-------------------------------------\n");
printf(" 一个简单的打字游戏,按ESC退出\n");
printf(" 建议提前切换成英文输入法\n");
printf("-------------------------------------\n");

int type;
int flag = 1;
while(flag)
{
printf("1-简单\n2-中等\n3-困难\n4-魔鬼\n");
printf("请选择你要打字的等级:");
while(scanf("%d", &type) != 1)
{
system("cls");
scanf("%*s");
printf("您输入的值有问题,请输入数字!\n");
printf("1-简单\n2-中等\n3-困难\n4-魔鬼\n");
printf("请选择你要打字的等级:");
}
switch(type)
{
case 1:
speed = 500;
bulletSpeed = 30;
flag = 0;
break;
case 2:
speed = 350;
bulletSpeed = 25;
flag = 0;
break;
case 3:
speed = 250;
bulletSpeed = 20;
flag = 0;
break;
case 4:
speed = 150;
bulletSpeed = 10;
flag = 0;
break;
default:
printf("你选择的值有误,请重新选择\n");
}
}
printf("请输入你要打字的数量:");
while(scanf("%d", &letterCount) != 1)
{
system("cls");
scanf("%*s");
printf("您输入的值有问题,请输入数字!\n");
printf("请输入你要打字的数量:");
}
printf("字母已准备就绪,");
system("pause");
system("cls");
}
//结束游戏提示
void gameOver()
{
gotoxy(30, 12);
printf("总字母:%d个 您击落:%d个 错误按键:%d次", letterCount, dropLetter, errorCount);
}

int main()
{
system("title 打字游戏");
gameBegin();
hideCursor();
InitializeCriticalSection(&csCursor);
init();
initLetters();
CreateThread(NULL, 0, runLetter, NULL, 0, NULL);
_beginthread(setTitle, 0, NULL);
while(beginFlag)
{
if(_kbhit())
{
char ch = _getch();
ch = toupper(ch);
if(ch == 27)
{
break;
}
int flag = 1;
for(int i = 0; i < letterCount; i++)
{
if(letters[i].y < 25 && letters[i].y >= 0 && letters[i].life == 1 && letters[i].ch == ch && visBullet[i] == 0)
{
flag = 0;
dropLetter++;
CreateThread(NULL, 0, runBullet, &bulletflag[i], 0, NULL);
break;
}
}
if(flag) errorCount++;
}
}
gameOver();
InitializeCriticalSection(&csCursor);
system("\n\n\npause");
return 0;
}

2019年4月26日更新日志:

1.增加了很多注释

2019年4月25日更新日志:

1.后续完全开发多线程版本,单线程版本不再进行大更新。