个人作品

合抱之木,生于毫末;九层之台,起于累土;千里之行,始于足下

程序员专用的打字练习程序(by 温良)

这是一个专门给初学 C 语言的同学用的练习打字的程序。

屏幕上会落下来几个单词(都是 C 语言的关键字),然后通过键盘输入任意一个单词,如果输入正确,下落的对应单词就会消失。

程序会统计实时的准确率和打字速度等数据。

执行效果如下:

完整的源代码如下:

//////////////////////////////////////////////////////
// 程序名称:C 语言码农专用的打字练习程序
// 编译环境:Visual C++ 2008, EasyX_20190415(beta) 
// 作  者:温良(大一)
//    QQ:1677376433
// 最后修改:2019-5-10
//
#include <graphics.h>
#include <conio.h>
#include <time.h>
#include <stdio.h>

const int WIDTH  = 640;			// 界面宽度
const int HEIGHT = 500;			// 界面高度


//////////////////////////////////////////////////////
// 定义游戏计时对象
class GameClock
{
private:
	clock_t clockBegin;
	clock_t clockNow;
	int		timeMax;
	int		timeNow;

public:
	// 构造函数(开始计时)
	GameClock(int max)
	{
		clockBegin = clock();
		clockNow = 0;
		timeMax = max;
		timeNow = max;
	}

	// 返回当前剩余秒数
	int Now()
	{
		return timeNow;
	}

	// 返回已经消耗的秒数
	int Elapse()
	{
		return timeMax - timeNow;
	}

	// 判断是否计时终止
	bool IsStop()
	{
		return timeNow > 0;
	}

	// 游戏主循环执行一次时钟周期
	void Tick()
	{
		clockNow = clock();
		timeNow = timeMax - (clockNow - clockBegin) / 1000;
	}
};


//////////////////////////////////////////////////////
// 定义文本框对象
class TextBox
{
private:
	int		m_x1, m_y1, m_x2, m_y2;		// 文本框位置
	wchar_t	m_text[10];					// 用户输入的字符串
	int		m_textlen;					// 用户输入字符串的长度
	int		m_fps;						// 每秒调用多少次(决定了光标闪烁速度)

public:
	// 构造函数
	TextBox(int x1, int  y1, int x2, int y2, int fps)
	{
		m_x1 = x1;	m_y1 = y1;	m_x2 = x2;	m_y2 = y2;
		m_text[0] = '\0';
		m_textlen = 0;
		m_fps = fps;
	}

	// 附加一个字符(可以接受退格)
	void Append(wchar_t c)
	{
		if (c != 8)
		{
			if (m_textlen < 9)
			{
				m_text[m_textlen] = c;
				m_text[m_textlen + 1] = '\0';
				m_textlen++;
			}
		}
		else
		{
			if (m_textlen > 0)
			{
				m_textlen--;
				m_text[m_textlen] = '\0';
			}
		}
	}

	// 返回输入的字符串指针
	wchar_t* Text()
	{
		return m_text;
	}

	// 清空输入的字符串
	void Empty()
	{
		m_textlen = 0;
		m_text[0] = '\0';
	}

	// 绘制文本框
	void Draw()
	{
		static int fps = 0;				// 统计字母个数计算光标位置

		// 设置文字输出样式
		settextstyle(20, 0, L"Verdana");

		// 画边框
		setlinecolor(WHITE);
		rectangle(m_x1, m_y1, m_x2, m_y2);

		// 输出用户输入的字符串
		outtextxy(m_x1 + 30, m_y1 + (m_y2 - m_y1 - 20) / 2, m_text);

		// 绘制光标 
		fps++;
		if (fps < (m_fps / 4))
		{
			setlinecolor(WHITE);
			int sx = m_x1 + 31 + textwidth(m_text);
			line(sx, m_y1 + (m_y2 - m_y1 - 20) / 2, sx, m_y1 + (m_y2 - m_y1 - 20) / 2 + 20);
		}
		else if (fps > (m_fps / 2))
			fps = 0;
	}
};


// 目标单词的结构体
struct TARGET
{
	int x;
	int y;
	wchar_t* p;
};


// 获取用户输入(仅接受英文字母和退格键)
wchar_t Input()
{
	wchar_t c = 0;

	if (_kbhit())
	{
		c = _getwch();
		if (c >= 'A' && c <= 'Z')
			c = c + 32;
		else if (!(c >= 'a' && c <= 'z') && c != 8)
			c = 0;
	}
	return c;
}


// 初始化目标单词
void InitTarget(TARGET words[], int n)
{
	static wchar_t ss[28][10] = { L"main", L"include", L"void", L"while", L"true", L"char", L"break", L"else", L"false", L"switch",
								L"double", L"struct", L"long", L"case", L"enum", L"return", L"union", L"const", L"float", L"for",
								L"unsigned", L"continue", L"default", L"short",	L"static", L"if", L"register", L"sizeof" };

	words[n].p = ss[rand() % 28];
	// 判断随机生成的字符串是否相同(相同就重新生成掉落)
	while (words[n].p == words[(n + 1) % 3].p || words[n].p == words[(n + 2) % 3].p)
		words[n].p = ss[rand() % 28];

	settextstyle(20, 0, L"Verdana");
	words[n].x = rand() % (WIDTH - 100 - textwidth(words[n].p));
	words[n].y = -20;
}


// 在指定位置输出整数
void outintxy(int x, int y, const wchar_t* format, int n)
{
	wchar_t tmp[11];

	swprintf(tmp, 11, format, n);
	outtextxy(x, y, tmp);
}


// 在指定位置输出浮点数
void outdoublexy(int x, int y, const wchar_t* format, double n)
{
	wchar_t tmp[11];

	swprintf(tmp, 11, format, n);
	outtextxy(x, y, tmp);
}


// 判断 src 是否是某个 word 的前缀
bool isprefix(wchar_t src[], TARGET words[])
{
	for (int i = 0; i < 3; i++)
	{
		if (wcsstr(words[i].p, src) == words[i].p)
			return true;
	}
	return false;
}


//////////////////////////////////////////////////////
// 游戏主循环
void Game()
{
	TextBox textbox(10, HEIGHT - 40, WIDTH - 110, HEIGHT - 10, 50);	// 文本框对象
	TARGET words[3];												// 掉落的 3 个单词
	GameClock gameclock(60);										// 游戏计时对象

	wchar_t	key;					// 用户输入的英文
	int		right = 0, wrong = 0;	// 用户输入的正确、错误计数
	int		v = 0;					// 打字速度
	double	s;						// 正确率

	// 随机生成掉落的字符串
	for (int n = 0; n < 3; n++)
	{
		InitTarget(words, n);
		words[n].y = -15 - n * 30;
	}

	// 游戏主循环
	while (gameclock.IsStop())
	{
		// 获取控制/////////////////////////////////////////////////////

		key = Input();


		// 计算////////////////////////////////////////////////////////

		// 执行一次游戏时钟
		gameclock.Tick();

		// 判断用户输入
		if (key != 0)
		{
			textbox.Append(key);

			// 判断用户输入是否正确并统计正确错误数
			if (key != 8)
			{
				if (isprefix(textbox.Text(), words))
					right++;
				else
					wrong++;
			}

			// 判断输入的字符串是否与掉落的字符串相等
			for (int i = 0; i < 3; i++)
				if (wcscmp(textbox.Text(), words[i].p) == 0)
				{
					InitTarget(words, i);
					textbox.Empty();
				}
		}

		// 计算打字速度
		v = (gameclock.Elapse() == 0) ? 0 : right * 60 / gameclock.Elapse();

		// 单词掉落并进行判断是否超出屏幕掉落范围
		for (int n = 0; n < 3; n++)
		{
			words[n].y += 2;

			if (words[n].y > HEIGHT - 40)
				InitTarget(words, n);
		}

		// 计算正确率
		if (wrong + right == 0)
			s = 0;
		else
			s = (double)right / (wrong + right) * 100;


		// 绘制////////////////////////////////////////////////////////

		// 清除屏幕
		cleardevice();

		// 设置输出字符串的显示格式
		settextstyle(20, 0, L"Verdana");
		setbkmode(TRANSPARENT);
		settextcolor(WHITE);

		// 绘制统计区域
		setfillcolor(RGB(50, 50, 50));
		solidrectangle(WIDTH - 100, 0, WIDTH - 2, HEIGHT);

		// 输出掉落的单词
		for (int n = 0; n < 3; n++)
			outtextxy(words[n].x, words[n].y, words[n].p);

		// 绘制 TextBox
		textbox.Draw();

		// 右侧正确输入统计
		outtextxy(WIDTH - 90, 25, L"正确数");
		outintxy(WIDTH - 80, 50, L"%d", right);

		// 右侧错误输入统计
		outtextxy(WIDTH - 90, 85, L"错误数");
		outintxy(WIDTH - 80, 110, L"%d", wrong);

		// 右侧正确率的计算
		outtextxy(WIDTH - 90, 145, L"正确率");
		outdoublexy(WIDTH - 80, 170, L"%.1f %%", s);

		// 右侧打字速度的计算
		outtextxy(WIDTH - 90, 205, L"打字速度");
		outintxy(WIDTH - 80, 230, L"%d kpm", v);

		// 右侧输出的倒计时
		outtextxy(WIDTH - 90, 265, L"倒计时");
		outintxy(WIDTH - 80, 290, L"%d s", gameclock.Now());

		FlushBatchDraw();

		Sleep(20);
	}
}


// 主函数
int main()
{
	srand((unsigned)time(NULL));
	initgraph(WIDTH, HEIGHT);

	BeginBatchDraw();

	while (true)
	{
		Game();

		// 提示游戏结束
		setfillcolor(RGB(200, 200, 200));
		solidrectangle(150, 200, 450, 300);
		settextcolor(BLACK);
		outtextxy(220, 225, L"重新开始请按 空格");
		outtextxy(220, 255, L"退出游戏请按 Esc");
		FlushBatchDraw();

		// 实现按空格重来、按 ESC 退出
		wchar_t ch = 0;
		while (ch != 27 && ch != ' ')
			ch = _getwch();

		if (ch == 27)
			break;
	}

	EndBatchDraw();

	closegraph();
	return 0;
}

作者:温良
QQ:1677376433
邮箱:1677376433@qq.com

分享到

添加评论