需求

比赛规则:

  • 学校举行一场演讲比赛,共有12个人参加,比赛共两轮,第一轮为淘汰赛,第二轮为决赛
  • 每名选手都有对应的编号,如10001~10012
  • 比赛方式:分组比赛,每组6个人
  • 第一轮分为两个小组,整体按照选手编号进行抽签后顺序演讲
  • 十个评委分别给每名选手打分,去除最高分和最低分,求的平均分为本轮选手的成绩
  • 当小组演讲完后,淘汰组内排名最后的三个选手,前三名晋级,进入下一轮的比赛
  • 第二轮为决赛,前三名胜出
  • 每轮比赛过后需要显示晋级选手的信息

程序功能:

  • 开始演讲比赛:完成整届比赛的流程,每个比塞阶段需要给用户一个提示,用户按任意键后继续下一个阶段
  • 查看往届记录:查看之前比赛前三名结果,每次比赛都会记录到文件中,文件用.csv后缀名保存
  • 清空比塞记录:将文件中数据清空
  • 退出比赛程序:可以退出当前程序

框架

先创建speechManager类,将功能集成到类中

创建空构造和析构函数避免可能出现的报错

1
2
3
4
5
6
7
8
9
10
11
#ifndef SPEECHMANAGER_H
#define SPEECHMANAGER_H
#pragma once
#include <iostream>
using namespace std;
class speechManager {
public:
speechManager();
~speechManager();
};
#endif //SPEECHMANAGER_H

speechManager类中添加属性

1
2
3
4
5
6
7
vector<int>one; // 第一轮比赛编号
vector<int>two; // 第二轮比赛编号
vector<int>victory; // 冠亚季编号
map<int,Speaker> speaker;
int Index; // 比赛轮次
bool fileisempty; // 判断文件是否为空
map<int,vector<string>> record; // 存放往届记录的容器

初始化代码写为一个函数,方便后续clear操作

1
2
3
4
5
6
7
8
void speechManager::init() {
this->one.clear();
this->two.clear();
this->victory.clear();
this->speaker.clear();
this->record.clear();
this->Index = 1;
}

菜单功能

1
2
3
4
5
6
7
8
void speechManager::showMenu() {
cout<<"Welcome to Speech Manager"<<endl;
cout<<"1. Start the speech contest"<<endl;
cout<<"2. View the past record"<<endl;
cout<<"3. Clear the record"<<endl;
cout<<"0. Exit the system"<<endl;
cout<<"Press any key to continue: ";
}

退出功能

1
2
3
4
5
void speechManager::exitSystem() {
cout << "Welcome to the next use!"<<endl;
system("pause");
exit(0);
}

创建选手

先创建选手类,选手属性只有名字和得分,得分可能有两轮写为数组形式

1
2
3
4
5
6
7
class Speaker {
public:
Speaker() = default;
~Speaker() = default;
string name;
double score[2]{};
};

speechManager类中添加创建函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void speechManager::createSpeaker() {
string name_tag = "ABCDEFGHIJKL";
for (int i = 0;i<name_tag.size();i++) {
string name = "Speaker";
// error: string name = "Speaker" + name_tag[i];
// "Speaker_"是char*类型,name_tag[i]是char类型, 无法直接加
// 进入string容器后+被重定义
name += name_tag[i];
Speaker sp;
sp.name = name;
this->one.push_back(10001+i);
this->speaker.insert(make_pair(10001+i, sp));
}
}

比赛

比赛流程为:第一轮抽签→评比分数→展示晋级→第二轮抽签→评比分数→展示成绩

然后将每部分实现即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void speechManager::startspeech() {
// 第一轮比赛开始
// 抽签
this->draw_lots();
// 比赛
this->speechcontest();
// 展示晋级成绩
this->showscore();
// 第二轮比赛开始
this->Index++;
// 抽签
this->draw_lots();
// 比赛
this->speechcontest();
// 展示晋级成绩
this->showscore();
// 保存分数到文件中
this->saverecord();
cout << "The speech contest is over!" << endl;
// 重置属性
this->reset(); // 少了这步会导致新完成的比赛读不到
system("pause");
system("cls");
}

抽签

把两轮的写到一个函数中,由Index来分类

抽签其实就是用shuffle函数打乱容器内元素

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
void speechManager::draw_lots() {
cout << "--------------------------" << endl;
cout << "Drawing of lots for round " << this->Index << endl;
cout << "The order as follow: " <<endl;
if (this->Index == 1) {
shuffle(this->one.begin(), this->one.end(),
mt19937(random_device()())); // 创建一个临时的 random_device 对象
for (int i = 0;i<this->one.size();i++) {
cout << this->one[i] << "\t";
if (i == this->one.size()/2-1) {
cout << endl;
}
}
cout << endl;
}
if (this->Index == 2) {
shuffle(this->two.begin(), this->two.end(),
mt19937(random_device()()));
for (const int & it : this->two)
cout << it << " ";
cout << endl;
}
cout << "--------------------------" << endl;
system("pause");
}

评比分数

由于第一轮第二轮每组比赛的人数都是6,可以写到一起

创建两个临时容器,一个用来存储成绩和编号键值对(成绩作为key方便排序),另一个用来存储比赛人员的编号

根据Index的值来读取系统中存储的比赛人员编号

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
void speechManager::speechcontest() {
cout << "--------------------------" << endl;
cout <<"Round " << this->Index << " speech contest started!"<<endl;
multimap<double,int,greater<>> groupscore; // 临时容器,存储成绩和编号
// 成绩放在前面是为了方便排序
int num = 0; //记录人员数
vector<int> v_Src; // 比赛人员容器
if (this->Index == 1)
v_Src = this->one;
if (this->Index == 2)
v_Src = this->two;
for (auto it = v_Src.begin(); it != v_Src.end(); it++) {
num++;
deque<double> d;
for (int i =0; i<10 ; i++) {
// 评委打分
random_device rd;
mt19937 gen(rd());
uniform_real_distribution<double> dist(60.0, 100.0);
double score = dist(gen);
// cout << score << "\t";
d.push_back(score);
}
sort(d.begin(), d.end());
// 去除最高分和最低分
d.pop_front();
d.pop_back();
// 计算平均分
double avg = accumulate(d.begin(), d.end(), 0.0) / static_cast<double>(d.size());
// *it是编号,将avg存入对应成绩数组
this->speaker[*it].score[this->Index-1] = avg;
groupscore.insert(make_pair(avg, *it));
if (num%6 == 0) {
cout << "Rank of group " << num/6 << ":" <<endl;
// 展示小组成绩
for (auto & it : groupscore) {
cout << it.second << "\t";
cout << this->speaker[it.second].name<< "\t";
cout << this->speaker[it.second].score[this->Index-1] << endl;
}
// 取出前三名
int count = 0;
for (auto it = groupscore.begin();
it != groupscore.end() && count < 3 ; it++, count++) {
if (this->Index==1) {
this->two.push_back(it->second);
}
if (this->Index==2) {
this->victory.push_back(it->second);
}
}
groupscore.clear();
cout << endl;
}
}
cout <<"Round " << this->Index << " speech contest complete!"<<endl;
cout << "--------------------------" << endl;
system("pause");
}

展示分数

第一轮晋级的取two容器中的内容,第二轮决赛的取victory容器中的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void speechManager::showscore() {
cout << "-------------------------" << endl;
cout << "The " << this->Index <<" round advanced contestants as follow: " <<endl;
vector<int> v_Src;
if (this->Index == 1) {
v_Src = this->two;
}
if (this->Index == 2) {
v_Src = this->victory;
}
for (int & it : v_Src) {
cout << it << " ";
cout << this->speaker[it].name<< "\t";
cout << this->speaker[it].score[this->Index-1] << endl;
}
cout << "--------------------------" << endl;
system("pause");
}

保存记录

添加头文件fstream,将结果写到csv文件中

1
2
3
4
5
6
7
8
9
10
11
#include <fstream>
void speechManager::saverecord() {
ofstream ofs;
ofs.open("speech.csv", ios::out|ios::app);
for (auto it : this->victory) {
ofs << it << "," << this->speaker[it].score[1] << ",";
}
ofs << endl;
ofs.close();
cout << "---- record save! ----" << endl;
}

查看记录

查看肯定要读取文件,这个时候又增加了标识符和新容器

1
2
bool fileisempty; // 判断文件是否为空
map<int,vector<string>> record; // 存放往届记录的容器

读取文件:

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
void speechManager::loadrecord() {
ifstream ifs;
ifs.open("speech.csv", ios::in);
if (!ifs.is_open()) {
this->fileisempty = true;
cout << "File does not exist!" << endl;
return;
}
char ch;
ifs >> ch;
if (ifs.eof()) {
this->fileisempty = true;
cout << "File is empty!" << endl;
ifs.close();
return;
}
// 文件不为空
fileisempty = false;
// 将刚刚读的放回去
ifs.putback(ch);
string data;
int index = 1; // 第index届
while (ifs>>data) {
// 当遇到换行符时会停止当前读取,将换行符留在输入流中
// 下一次循环时会跳过换行符,读取下一行内容
vector<string> record; // 创建存储空间,每次要清空
int pos = -1; // 查,位置
int start = 0;
while (true) {
pos = data.find(",",start);
if (pos == -1) {
break;
}
string temp = data.substr(start, pos-start);
start = pos+1;
record.push_back(temp);
}
this->record.insert(make_pair(index,record));
index++;
}
ifs.close();
}

先判断文件是否为空,不为空则读取其中内容,并按键值读出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void speechManager::showrecord() {
if (this->fileisempty) {
cout << "File is empty or does exist!" << endl;
system("pause");
system("cls");
}
else {
for (auto & it : this->record) {
cout << "Result of No." << it.first << " speech contest:" <<endl;
for (int i = 0; i < it.second.size(); i++) {
cout << it.second[i] << " ";
if (i%2==1)
cout << endl;
}
}
system("pause");
system("cls");
}
}

清空记录

为了方便清空以及之前的一些刷新,新增reset函数

方便一些需要刷新内存的情况,比如完成一场比赛后如果不刷新会查询不到新的记录

1
2
3
4
5
6
7
8
void speechManager::reset() {
this->init();
this->createSpeaker();
this->loadrecord();
}
speechManager::speechManager() {
this->reset();
}

清空操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void speechManager::clearrecord() {
cout << "Sure about deletion?" << endl;
cout << "1.yes\t2.no" << endl;
int select;
cin >> select;
if (select == 1) {
ofstream ofs("speech.csv",ios::trunc);
ofs.close();
this->reset();
cout << "Success delete!" << endl;
}
else {
cout << "User cancle" << endl;
}
system("pause");
system("cls");
}