部分效果图:
配置文件如下:
加入新题库时,需要新建一个空文件,并把题目添加到新文件中,并把新文件名填入配置文件中。
/*******************************************************************?
*
* File Name: do-test
* Sketch out: A simple multiple-choice system
*
* Date created: 2019/12/18
* Author: YWR
*
******************************************************************/
#include
#include
#include
#include
#include
#include
#include
#define MAX_TOPIC 1024
#define MAX_OPTION 512
#define MAX_ITEM 50
#define CONFIG_FILE ".\\config.ini"
// Bring white
#define FINEWHITE \
FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE
// Bring red
#define AZARIN \
FOREGROUND_INTENSITY | FOREGROUND_RED
typedef struct _Config {
char *test_file[MAX_PATH]; // test-file list
char *wrong_file[MAX_PATH]; // wrong-file list
int test_file_num; // test-file total
int wrong_file_num; // wrong-file total
int item_num; // file subject total
} Config, *PConfig;
typedef struct _Item {
int serial_num; // serial_num
char topic[MAX_TOPIC]; // topic
char option[4][MAX_OPTION]; // options
char answer[2]; // answer
} Item, *PItem;
static Item subject[MAX_ITEM];
static Config config;
void trim_config(char *s);
bool read_config();
void level_one_menu();
void level_two_menu();
void get_setting();
char *get_test_mark();
int read_test(const char *pfile);
int get_corrent(int min, int max);
bool file_is_null(FILE *tmp);
void clean_test();
int get_int();
bool add_subject(const char *pfile);
void free_file_name();
void set_cmd_color();
/** read_test: read file items
* If return -1: can't found file
* If return 0: read file is error
* If return 1: read file is ok
*/
int read_test(const char *pfile)
{
// Opened test-file
FILE *in = NULL;
in = fopen(pfile, "r+b");
if (!in)
{
fprintf(stderr, "无法找到 %s.\n",
pfile);
return -1;
}
// If file is null
if (file_is_null(in))
{
fprintf(stderr, "%s 是空的.\n",
pfile);
// Clean struct
config.item_num = 0;
clean_test();
// Closed file
fclose(in);
return false;
}
int count = 0;
// Read subject
rewind(in);
while (count < MAX_ITEM && (!feof(in)))
{
if (fread(&subject[count], 1, sizeof(Item), in)
!= sizeof(Item))
break;
count++;
}
// If Read is error
if (count == 0)
{
clean_test();
fprintf(stderr, "读取了 0 条项目\n");
return false;
}
// If Read is ok
else
{
config.item_num = count;
printf("读取了 %d 条项目\n", count);
}
// Closed test-file
if (fclose(in))
{
fprintf(stderr, "无法关闭 %s.\n",
pfile);
return false;
}
return true;
}
/** trim_config: delete string last '\n'
* no return value
*/
void trim_config(char *s)
{
int n;
for (n = strlen(s)-1; n >= 0; n--)
{
if (s[n]!=' ' && s[n]!='\t' && s[n]!='\n')
break;
s[n]='\0';
}
}
/** free_file_name: free heap space for names
* no return value
*/
void free_file_name()
{
int i;
for (i = 0; i < config.test_file_num; i++)
free(config.test_file[i]);
for (i = 0; i < config.wrong_file_num; i++)
free(config.wrong_file[i]);
}
/** read_config: read configuration files
* If return 0: read error or can't found file
* If return 1: found file and read ok
*/
bool read_config()
{
// Opened config-file
FILE *in = NULL;
in = fopen(CONFIG_FILE, "r+b");
if (!in)
{
fprintf(stderr, "无法找到 config-ini.\n");
return false;
}
// order: Said file
// param: config-file data
// annontation: comments mark
int order = 0;
char param[256];
int param_len = 0;
int annontation = 0;
int test_count, wrong_count;
test_count = wrong_count = 0;
/* Read configuration file and analyze */
while (fgets(param, 256, in) != NULL)
{
trim_config(param);
// Filter out comments
if (param[0] != '#' && (param[0] != '/' || param[1] != '/'))
{
if (strstr(param, "/*") != NULL)
{
annontation = 1;
continue;
}
else if (strstr(param, "*/") != NULL)
{
annontation = 0;
continue;
}
}
if (annontation == 1)
continue;
param_len = strlen(param);
if (param_len <= 1 || param[0] == '#' || param[0] == '/' || param[0] == '=')
{
order++;
continue;
}
// Param: *.txt'\n''\0'
// first '\n' is in file
param[param_len-1] = '\0';
/* If order is 1, it's test-file
* If order is 2, it's wrong-file */
if (order == 1)
{
// *.txt'\0' len is 5+1
config.test_file[test_count] = (char *)malloc((strlen(param)+1)*sizeof(char));
strcpy(config.test_file[test_count], param);
test_count++;
config.test_file_num = test_count;
}
else if (order == 2)
{
config.wrong_file[wrong_count] = (char *)malloc((strlen(param)+1)*sizeof(char));
strcpy(config.wrong_file[wrong_count], param);
wrong_count++;
config.wrong_file_num = wrong_count;
}
else // Read file is error, over two #
return false;
}
// Closed config-file
if (fclose(in))
{
fprintf(stderr, "无法关闭 config-ini\n");
return false;
}
return true;
}
/** file_is_null: determine if the file is null
* If return 1: file is null
* If return 0: file isn't null
*/
bool file_is_null(FILE *tmp)
{
char ch;
ch = getc(tmp);
if (ch == EOF)
return true;
return false;
}
void clean_test()
{
int i;
for (i = 0; i < MAX_ITEM; i++)
memset(&subject[i], 0, sizeof(Item));
}
void set_cmd_color(int forecolor)
{
// Get output handle
handle_t std_out = GetStdHandle(STD_OUTPUT_HANDLE);
// Set CMD color
SetConsoleTextAttribute(std_out, forecolor);
}
/** begin_test: traverse the structure that holds the data
* no return value
*/
void begin_test()
{
system("cls");
int index, ops;
int count = 0;
char answer[config.item_num][2];
while (count < config.item_num)
{
printf("%d. %s\n", subject[count].serial_num,
subject[count].topic);
ops = 0;
while (ops < 4)
puts(subject[count].option[ops++]);
answer[count][0] = toupper(getch());
while (answer[count][0] < 'A' || answer[count][0] > 'D')
answer[count][0] = toupper(getch());
answer[count++][1] = '\0';
system("cls");
}
int is_ok = 0;
// Give a mark
for (index = 0; index < config.item_num; index++)
{
if (strcmp(answer[index], subject[index].answer) == 0)
{
is_ok++;
set_cmd_color(FINEWHITE);
}
else
set_cmd_color(AZARIN);
printf("%2d:%-2s", index+1, answer[index]);
set_cmd_color(FINEWHITE);
printf("-%-2s", subject[index].answer);
if ((index+1)%5==0)
putchar('\n');
}
printf("您的总分:%3d [%d/%d(%.2f%%)]\n", is_ok*3, is_ok, config.item_num,
100.0*((float)is_ok/config.item_num));
set_cmd_color(FINEWHITE);
getchar();
}
/** get_int: gets a character from the buffer, and delete '\n'
* return large letter
*/
int get_int()
{
int ch;
ch = getchar();
// Clean memory
while (getchar() != '\n')
continue;
return towupper(ch);
}
/** add_subject: add an item to the file
* If return 0: can't found file or file is full
* If return 1: add an item to the file success
*/
bool add_subject(const char *pfile)
{
FILE *in = NULL;
in = fopen(pfile, "r+b");
if (!in)
{
fprintf(stderr, "无法找到 %s.\n",
pfile);
return false;
}
int index, ops;
int count = config.item_num;
// If file is full
if (count == MAX_ITEM)
{
fprintf(stderr, "%s 是满的 <50>.\n",
pfile);
return false;
}
// Add new subject
puts("输入添加的题目:");
while (count < MAX_ITEM && gets(subject[count].topic) != NULL
&& subject[count].topic[0] != '\0')
{
ops = 0;
// Read option [1,4]
while (ops < 4)
{
printf("输入添加的选项[%d]:", ops+1);
gets(subject[count].option[ops]);
ops++;
}
puts("输入题目的答案:");
gets(subject[count].answer);
// 'c' convert into 'C'
subject[count].answer[0] = towupper(subject[count].answer[0]);
subject[count].answer[1] = '\0';
// serial_num [1,50]
subject[count].serial_num = count+1;
count++;
// If over max-items
if (count < MAX_ITEM)
puts("请输入下一个题目:");
}
// If file is not null
if (count > 0)
{
rewind(in);
puts("Here is the list of your subjects:");
for (index = 0; index < count; index++)
{
printf("%d. %s\n", subject[index].serial_num,
subject[index].answer);
fwrite(&subject[index], 1, sizeof(Item), in);
}
}
else
puts("没有题目? 太槽糕了.\n");
// Closed test-file
if (fclose(in))
{
fprintf(stderr, "无法关闭 %s.\n",
pfile);
return false;
}
return true;
}
/** modity_subject: modity an item to the file
* If return 0: can't found file or file is null
* If return 1: modity an item to the file success
*/
bool modity_subject(const char *pfile)
{
FILE *in = NULL;
in = fopen(pfile, "r+b");
if (!in)
{
fprintf(stderr, "无法找到 %s.\n",
pfile);
return false;
}
int index;
int count = config.item_num;
// If file is null
if (count == 0)
{
fprintf(stderr, "%s 是满的 <50>.\n",
pfile);
return false;
}
// If ch not in [1,50]
int tmp_num;
printf("输入一个编号去查找(1-%d):",
count);
tmp_num = get_corrent(1, count);
for (index = 0; index < count; index++)
{
if (subject[index].serial_num == tmp_num)
{
puts("输入一个新的答案:");
gets(subject[index].answer);
// small leter convert into large leter
subject[index].answer[0] = toupper(subject[index].answer[0]);
subject[index].answer[1] = '\0';
fseek(in, index * sizeof(Item), SEEK_SET);
fwrite(&subject[index], 1, sizeof(Item), in);
break;
}
}
// Closed test-file
if (fclose(in))
{
fprintf(stderr, "无法关闭 %s.\n",
pfile);
return false;
}
return true;
}
/** show submenu */
void level_two_menu()
{
puts("<--子菜单-->");
puts("1. 添加题目");
puts("2. 修改题目");
puts("Q. 返回主菜单");
}
/** get_setting: gets a select change file
* no return value
*/
void get_setting()
{
int choices;
char *pfile = NULL;
level_two_menu();
while ((choices = get_int()) != 'Q')
{
switch (choices)
{
case '1': // add subject
pfile = get_test_mark();
if (read_test(pfile) != -1)
add_subject(pfile);
break;
case '2': // modify subject
pfile = get_test_mark();
if (read_test(pfile) != -1)
modity_subject(pfile);
break;
case 'q':
return;
default:
puts("您必须输入 1-2 或者退出\n");
}
level_two_menu();
}
}
/** show_enable_file: show enable the file
* no return value
*/
void show_enable_file(char n)
{
int i;
printf("关于 %s 的列表:\n",n=='1'?"Test":"Wrong");
if (n == '1')
for (i = 0; i < config.test_file_num; i++)
{
printf("%d:[%s] ", i+1,config.test_file[i]);
if ((i+1) % 5 == 0)
putchar('\n');
}
else if (n == '2')
for (i = 0; i < config.wrong_file_num; i++)
{
printf("%d:[%s] ", i+1,config.wrong_file[i]);
if ((i+1) % 5 == 0)
putchar('\n');
}
printf("\n\n");
}
/** get_corrent: get a corrent range
* return a number
*/
int get_corrent(int min, int max)
{
int n;
scanf("%d", &n);
while (n < min || n > max)
{
printf("您必须输入 %d-%d 的范围:",
min,max);
scanf("%d", &n);
}
while (getchar() != '\n')
continue;
return n;
}
/** change_enable_file: gets the file to select
* return a file pointer
*/
char *change_enable_file(char n)
{
int c;
char *tmp_file = NULL;
if (n == '1')
{
c = get_corrent(1, config.test_file_num);
tmp_file = config.test_file[c-1];
}
else if (n == '2')
{
c = get_corrent(1, config.wrong_file_num);
tmp_file = config.wrong_file[c-1];
}
return tmp_file;
}
/** get_test_mark: And "change_enable_file" combination
* return a file pointer
*/
char *get_test_mark()
{
int ch;
puts("您想要选择哪种类型的文件?");
puts("1. 考试文件 2. 错题文件");
ch = get_int();
// Choices <1 or 2>
show_enable_file(ch);
// Load test-file
return change_enable_file(ch);
}
/** show main menu */
void level_one_menu()
{
puts("<--主菜单-->");
puts("A. 开始考试");
puts("B. 设置文件");
puts("Q. 退出");
}
int main()
{
int choices;
char *pfile = NULL;
if (!read_config())
{
fprintf(stderr, "读取配置信息错误.\n");
return 1;
}
// show main-menu
level_one_menu();
while ((choices = get_int()) != 'Q')
{
switch (choices)
{
case 'A':
pfile = get_test_mark();
if (read_test(pfile) == true)
begin_test();
break;
case 'B':
get_setting();
break;
case 'Q':
fprintf(stderr, "程序错误.\n");
exit(1);
}
level_one_menu();
}
// free
free_file_name();
puts("再会.");
return 0;
}