c++编写评测机(1)

目录

前言

实现思路

具体实现

评测机完善

使用方法

完整代码


前言

最近闲得无聊,想用c++自己写一个评测机。一来为了提升个人c++实际应用能力,二来也能学习到不少知识。(其实纯属是为了玩儿)。

本评测机用纯c++编写,平台:devc++。可评测c++/c的程序,编译基于Mingw。由于用纯c++编写,什么乱七八糟的库都没用,所以本评测机会很(方便使用)粗糙,但对于简单的评测来说绰绰有余。模拟场景也没有那么细致,不过,开源嘛,有兴趣的读者可以自行改进,毕竟我写的没有那么好。

实现思路

大体思路就是对拍,不过有很多可以细化的东西。

1.找到指定地址中的目标文件(std.cpp,ans.cpp,存时、空限制和分值的txt,测试数据等等),读取测试数据。

2.分别编译ans.cpp,std.cpp(一个system函数就好了)ps:后面的版本可能会有两种评测方式,一种是两个cpp对拍,一种是cpp和给定的txt答案对拍。

3.将测试数据分别输入两个exe中(只要在cpp文件里加入文件读写就好了),得到输出存入txt中,将两个输出比较。

4.重复3,就可以实现多组测试数据了。

5.后期版本会支持多题测试以及多名选手测试,实用性也会提高。

具体实现

下面我们来具体实现。

对于最初始的版本,我们先把ans.cpp、std.cpp、textpoint.txt和测试样例和评测机放在同一个文件夹下,后续再进行优化。这样我们就可以直接对文件进行操作,不需要获得文件的地址了。

首先我们先编译ans.cpp和std.cpp,让他们生成相应的exe文件。我们需要用到一个函数:

system("g++ -o 目标文件.exe 目标文件.cpp")

这个函数可以对目标cpp文件进行编译,并生成.exe文件。

但是文件也有可能编译错误,这时候就要进行特判。定义一个status_ans变量记录ans.cpp的编译结果。同样,status_std来记录std.cpp的编译结果。

if(status_ans == -1 || status_std == -1 || WIFEXITED(status_ans) == false || WIFEXITED(status_std) == false || 0 != WEXITSTATUS(status_ans) || 0 != WEXITSTATUS(status_std))

用这个语句判断编译错误。若编译错误,则输出”CE“。

编译时还要对电脑是64为还是32为进行判断。具体判断方法:

if(sizeof(char*) == 4)

那么就是32位,否则为64位。如果是32位,那么就要将编译函数改为

system("g++ -o std.exe -m32 std.cpp")

以上是编译部分。编译成功后就要进行评测,评测之前还要读取测试点信息,如测试点时间限制,分值,以及测试点个数。这些都将提前存放在textpoint.txt中。

为了方便读取,存放是我们按一定规则:第一行是测试点个数,接每个测试数据第一行为# + n + 冒号,就代表以下是第n个测试点的数据。每个测试点的数据包含两个整数1:timelimit(单位ms),2:score(自定义数值),中间用一个逗号分开。如下:

3
#1:
100,30
#2:
600,30
#3:
600,40

这就代表有3个数据,第一个时间限制为100毫秒,分值为30分,以此类推。

这是获得测试数据的函数:

void getpoint(const char *filename){
	int i, j = 0;
	FILE *p;
    p = fopen(filename, "r");
    fscanf(p, "%[^$]", textpoint);
    fclose(p);
    for(int i = 0; i < strlen(textpoint); i++){
        textpoint[j++] = textpoint[i];
    }
    textpoint[j] = 0;
    int f;
	for(int i = 0; i < strlen(textpoint); i++){//获得测试点个数
		if(textpoint[i] == '#'){
			f = i;
			break;
		}
		if(textpoint[i] >= '0' && textpoint[i] <= '9')
			cnt = cnt * 10 + textpoint[i] - '0';
	}
	int now = 0;
	for(int i = f; i <= strlen(textpoint); i++){//获取测试点时间限制及分数
        if(textpoint[i] == '#'){
        	now++;
		}
		else if(textpoint[i] == ':'){
			int nowlimit = 0;
			for(int j = i; j <= strlen(textpoint); j++){
				if(textpoint[j] == ','){
					int nowpoint = 0;
                    for(int k = j; k <= strlen(textpoint); k++){
                    	if(textpoint[k] == '#'){
                    		break;
						}
						if(textpoint[k] <= '9' && textpoint[k] >= '0'){
							nowpoint = nowpoint * 10 + textpoint[k] - '0';
						}
					}
					sample[now].point = nowpoint;
					break;
				}
				if(textpoint[j] >= '0' && textpoint[j] <= '9') nowlimit = nowlimit * 10 + textpoint[j] - '0';
			}
			sample[now].time_limit = nowlimit;
		}
	}
}

获得测试数据后,我们就对每一个测试点进行评测。首先要获得输入数据,我们规定每个输入数据名称格式为:sample + 数字 + .in,例如sample1.in即为第一个测试点的输入数据。

有了测试数据文件名称后,我们把输入数据存入test.in中,方便.cpp文件读取。存入数据这个操作需要掌握文件操作与字符串,用fscanf()函数将sample中的内容读到字符串s中,如果想要忽略空格就把s中的“\n”删除。具体看代码:

void printsample(const char *filename, const char *filename2){
	int i, j = 0;
	char s[10000];
	FILE *p;
    p = fopen(filename, "r");
    fscanf(p, "%[^$]", s);
    fclose(p);
    for(int i = 0; i < strlen(s); i++){
        s[j++] = s[i];
    }
    s[j] = 0;
	p = fopen(filename2, "w");
    fprintf(p, "%s", s);
    fclose(p);
    return;
}

然后只需要运行.exe文件

system("ans.exe")

就能在ans.out和std.out中看见输出了(真是万能的system)。我们还要判断RE,与判断CE类似。

outcheck_std = system("std.exe");
		if(outcheck_std == -1 || outcheck_ans == -1 || WIFEXITED(outcheck_std) == false || WIFEXITED(outcheck_ans) == false || 0 != WEXITSTATUS(outcheck_std) || 0 != WEXITSTATUS(outcheck_ans)){
			color(5);
			cout << "RE";
			color(7);
			cout << "(" << elapsed_secs << "ms)\n";
			continue;
		}

最后我们将两个输出进行对比,如果相同,记为AC,并且加上对应测试点的分数,否则WA,还有忽略空格、换行什么的,再进行处理就行了。这是处理输出的函数

void enter_deal(const char* filename, int Type = 0){
	int i, j = 0;
	char s[10000];
	FILE *p;
    p = fopen(filename, "r");
    fscanf(p, "%[^$]", s);
    fclose(p);
    for(int i = 0; i < strlen(s); i++){
        if(Type == 0 && s[i] == ' ' || s[i] == '\n')
			continue;
        s[j++] = s[i];
    }
    s[j] = 0;
    if(Type == 0){
    	p = fopen(filename, "w");
	    fprintf(p, "%s", s);
	    fclose(p);
	}
    return;
}

然后将处理好的字符串对比就行了。

我们还有一个数据没有用到:时间限制。怎么实现呢?我们只要在运行ans.exe之前记录一次时间,运行后再记录一次,两次详见,就能获得运行时间了(单位:ms)。具体如下:

start = clock();
outcheck_ans = system("ans.exe");
end = clock();//用end-start(注意是double),就能获得时间了。

获得了运行时间后再与测试数据中的时间限制进行对比,就能判断是否TLE了。

另外,ans.cpp和std.cpp的main函数中要加入这么两行

freopen("test.in","r",stdin);
freopen("ans.out","w",stdout);

参加过csp的同学应该知道,这是文件读取。方便从test.in中读取输入内容,并将答案输出到ans.out/std.out中,方便评测机进行评测。

评测机完善

最后还有一点小细节,算是对评测机的完善。因为评测及到现在还是很粗糙的,所以我们来精细一下。

比如我们可以删除用户快速编辑(就是鼠标选中功能),这样来更好的模拟软件的界面,而不是控制台界面,但这知识相对于windows用户而言的。以下是相关函数:

void Clear_Qiuckedit(){
	HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
	DWORD mode;
	GetConsoleMode(hStdin, &mode);
	mode &= ~ENABLE_QUICK_EDIT_MODE;
	SetConsoleMode(hStdin, mode);
}

还有就是隐藏鼠标,让界面看起来更加图形化

void hide(int hide_type = 0){
	CONSOLE_CURSOR_INFO cursor_info = {1, hide_type};
	SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE),&cursor_info);
}

我们还可以通过改变字体颜色,对于每个测试结果(AC,WA,TLE...)都输出不同的颜色,这样看起来更加美观,也更有评测机那味儿,而不是像bat。

void color(int x, bool intensity = true){
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),
    x | (intensity << 3));
}

再附上最后的效果图:

 怎么样,是不是有那味了。

使用方法

评测机到现在差不多完善了,后续也会有改进版本。下面来介绍一下使用方法。

首先,新建一个文件夹,把评测机放进去,在把待评测的cpp文件放进去,并命名为ans.cpp,再把标程放进去,命名为std.cpp。在ans.cpp的主函数第一行中加入以下代码:

freopen("test.in","r",stdin);
freopen("ans.out","w",stdout);

在std.cpp的主函数第一行加入以下代码:

freopen("test.in","r",stdin);
freopen("std.out","w",stdout);

然后,新建一个txt文件,命名为textpoint.txt。在里面按照格式写入测试数据,具体格式上文有提到过,这里就不再赘述了。

最后,根据测试点的个数,新建对应个数的.in文件,分别命名为sample1.in,sample2.in等等,代表第一个输入数据,第二个输入数据...将输入数据写入.in文件中。

评测机环境就配置好了,最后只要运行评测机,就可以进行评测了。

完整代码

最后就是喜闻乐见的代码了。

//v1.0.0
#include<bits/stdc++.h>
#include <windows.h>
#define WEXITSTATUS(status)   (((status) & 0xff00) >> 8)
#define WIFEXITED(status) ((status & 0x7f) == 0)
using namespace std;
char textpoint[10000];
struct node{
	int time_limit, point;
}sample[1005];
int cnt, totle;
int outcheck_std, outcheck_ans;
const int maxn = 100005;
char ANS[maxn], STD[maxn] = "";
clock_t start, end;
double elapsed_secs;
char s = 'k';
const char *name1 = "test.in";
string samplename = "sample";
void Clear_Qiuckedit(){
	HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
	DWORD mode;
	GetConsoleMode(hStdin, &mode);
	mode &= ~ENABLE_QUICK_EDIT_MODE;
	SetConsoleMode(hStdin, mode);
}
void hide(int hide_type = 0){
	CONSOLE_CURSOR_INFO cursor_info = {1, hide_type};
	SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE),&cursor_info);
}
void color(int x, bool intensity = true){
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),
    x | (intensity << 3));
}
void gotoxy(int x, int y){
	COORD pos = {y - 1, x - 1};
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}
void print(string s, int n){
	gotoxy(1, 1);
	cout << "状态:";
	color(n);
	cout << s << "          " << endl;
	color(7);
}
void enter_deal(const char* filename, int Type = 0){
	int i, j = 0;
	char s[10000];
	FILE *p;
    p = fopen(filename, "r");
    fscanf(p, "%[^$]", s);
    fclose(p);
    for(int i = 0; i < strlen(s); i++){
        if(Type == 0 && s[i] == ' ' || s[i] == '\n')
			continue;
        s[j++] = s[i];
    }
    s[j] = 0;
    if(Type == 0){
    	p = fopen(filename, "w");
	    fprintf(p, "%s", s);
	    fclose(p);
	}
    return;
}
void getpoint(const char *filename){
	int i, j = 0;
	FILE *p;
    p = fopen(filename, "r");
    fscanf(p, "%[^$]", textpoint);
    fclose(p);
    for(int i = 0; i < strlen(textpoint); i++){
        textpoint[j++] = textpoint[i];
    }
    textpoint[j] = 0;
    int f;
	for(int i = 0; i < strlen(textpoint); i++){//获得测试点个数
		if(textpoint[i] == '#'){
			f = i;
			break;
		}
		if(textpoint[i] >= '0' && textpoint[i] <= '9')
			cnt = cnt * 10 + textpoint[i] - '0';
	}
	int now = 0;
	for(int i = f; i <= strlen(textpoint); i++){//获取测试点时间限制及分数
        if(textpoint[i] == '#'){
        	now++;
		}
		else if(textpoint[i] == ':'){
			int nowlimit = 0;
			for(int j = i; j <= strlen(textpoint); j++){
				if(textpoint[j] == ','){
					int nowpoint = 0;
                    for(int k = j; k <= strlen(textpoint); k++){
                    	if(textpoint[k] == '#'){
                    		break;
						}
						if(textpoint[k] <= '9' && textpoint[k] >= '0'){
							nowpoint = nowpoint * 10 + textpoint[k] - '0';
						}
					}
					sample[now].point = nowpoint;
					break;
				}
				if(textpoint[j] >= '0' && textpoint[j] <= '9') nowlimit = nowlimit * 10 + textpoint[j] - '0';
			}
			sample[now].time_limit = nowlimit;
		}
	}
}
void printsample(const char *filename, const char *filename2){
	int i, j = 0;
	char s[10000];
	FILE *p;
    p = fopen(filename, "r");
    fscanf(p, "%[^$]", s);
    fclose(p);
    for(int i = 0; i < strlen(s); i++){
        s[j++] = s[i];
    }
    s[j] = 0;
	p = fopen(filename2, "w");
    fprintf(p, "%s", s);
    fclose(p);
    return;
}
int main(){
	Clear_Qiuckedit();
	hide();
	color(7);
	FILE *fp;
	if((fp = fopen("textpoint.txt", "r")) == NULL){
		printf("请创建textpoin.txt并将测试点数据存入\n");
		return 0;
	}
	fclose(fp);
	getpoint("textpoint.txt");
	print("提交中", 7);
	int i = 0, j = 0;
    gotoxy(1, 1);
    print("编译中", 7);
	if(sizeof(char*) == 4){
		int status_std32 = 0, status_ans32 = 0;
		status_std32 = system("g++ -o std.exe -m32 std.cpp");
		status_ans32 = system("g++ -o ans.exe -m32 ans.cpp");
		if(status_ans32 == -1 || status_std32 == -1 || WIFEXITED(status_ans32) == false || WIFEXITED(status_std32) == false || 0 != WEXITSTATUS(status_ans32) || 0 != WEXITSTATUS(status_std32)){
			print("CE", 6);
			return 0;
		}
	}
	if(sizeof(char*) == 8){
		int status_std = 0, status_ans = 0;
		status_std = system("g++ -o std.exe std.cpp");
		status_ans = system("g++ -o ans.exe ans.cpp");
		if(status_ans == -1 || status_std == -1 || WIFEXITED(status_ans) == false || WIFEXITED(status_std) == false || 0 != WEXITSTATUS(status_ans) || 0 != WEXITSTATUS(status_std)){
			print("CE", 6);
			return 0;
		}
	}
	print("评测中", 7);
	for(int i = 1; i <= cnt; i++){
		cout << "#" << i << ": ";
	    char temp[10000];
	    sprintf(temp, "%d", i);
		string toname = samplename + temp + ".in";
		const char *name2 = toname.c_str();
		printsample(name2, name1);
		outcheck_std = system("std.exe");
		start = clock();
		outcheck_ans = system("ans.exe");
		end = clock();
		elapsed_secs = static_cast<double>(end - start);
		if(outcheck_std == -1 || outcheck_ans == -1 || WIFEXITED(outcheck_std) == false || WIFEXITED(outcheck_ans) == false || 0 != WEXITSTATUS(outcheck_std) || 0 != WEXITSTATUS(outcheck_ans)){
			color(5);
			cout << "RE";
			color(7);
			cout << "(" << elapsed_secs << "ms)\n";
			continue;
		}
		if(elapsed_secs > sample[i].time_limit){
			color(3);
			cout << "TLE";
			color(7);
			cout << "(" << elapsed_secs << "ms)\n";
			continue;
		}
		enter_deal("ans.out");
		enter_deal("std.out");
		int count_ans = 0, count_std = 0;
		freopen("std.out", "r", stdin);
			scanf("%s", STD);
		freopen("ans.out", "r", stdin);
			scanf("%s", ANS);
		int len_a = strlen(ANS);
		int len_s = strlen(STD);
		if(strcmp(ANS, STD) == 0){
			color(2);
			cout << "AC";
			color(7);
			cout << "(" << elapsed_secs << "ms)\n";
			totle += sample[i].point;
		}
		else{
			color(4);
			cout << "WA";
			color(7);
			cout << "(" << elapsed_secs << "ms)\n";
		}
	}
	cout << "分数:" << totle << endl;
	return 0;
}

代码具体实现过程在上文都已经讲了,还有不懂得可以在评论区论里问我。里面还有很多小细节没有讲到,不过看代码应该能看懂,最主要是学习嘛。代码中有不少关于文件操作的函数,相信理解了代码之后,你关于文件操作也能掌握不少。

还有就是,都看到最后了,留下你的三连再走呗~。

  • 9
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值