C++項目之演講比賽模擬

演講系統(SpeechContext)小結

前記

最近把C++ 的知識點過了一遍,雖然說不能完全的瞭解C++語言的特性,但是也發現了它的美,後面想要學習C++的熱情高漲,故完成了一個小項目,來達到溫故知新的效果吧。

主要涉及知識點:類、STL等

需求

1.比賽規則

  • 學校舉行一場演講比賽,共有12個人參加。比賽共兩輪,第一輪爲淘汰賽,第二輪爲決賽。
  • 每次比賽共有10名裁判分別對每個選手進行打分。
  • 比賽方式:分組比賽,每組6個人;選手每次要隨機分組,進行比賽
  • 每名選手都有對應的編號,如 10001 ~ 10012
  • 第一輪分爲兩個小組,每組6個人。 整體按照選手編號進行抽籤後順序演講。
  • 當小組演講完後,淘汰組內排名最後的三個選手,前三名晉級,進入下一輪的比賽。
  • 第二輪爲決賽,前三名勝出
  • 每輪比賽過後需要顯示晉級選手的信息

2.程序功能

  • 開始演講比賽:完成整屆比賽的流程,每個比賽階段需要給用戶一個提示,用戶按任意鍵後繼續下一個階段
  • 查看往屆記錄:查看之前比賽前三名結果,每次比賽都會記錄到文件中,文件用.csv後綴名保存
  • 清空比賽記錄:將文件中數據清空
  • 退出比賽程序:可以退出當前程序

需求分析與功能設計

在這之前,我們首先創建兩個類:

第一個類表示比賽選手類,記錄了當前比賽的選手信息,如選手編號,以及分數等,記作speaker.h,只是需要當作頭文件即可;

第二個類表示比賽流程類,記錄了整個比賽的信息,如比賽流程,記錄顯示等等,同樣的,我們將其聲明和其實現分開,分別記作speechmanager.hspeechmanager.cpp

1.總體功能面板的設計

首先,觀察可知,比賽總共的可以分爲四個分支,那麼總體main函數裏面可以採用分支結構,switch…case…語句來完成,可以按照項目所給的需求得到順序(也可以按照自己喜歡的)。


        switch(choice){
            case 1:  //開始比賽
                break;
            case 2:  //查看記錄
                break;
            case 3:  //清空記錄
                break;
            case 0:  //退出系統
                break;
            default:
                system("cls"); //清屏
                break;
        }

得到了上面的這個流程,我們需要給用戶提示功能,可以設計一個展示這個數字的含義的方法,可以將其加入到頭文件中聲明,cpp文件中實現:

void SpeechManager::showMenu() {
    cout << "********************************************" << endl;
    cout << "*************  歡迎參加演講比賽 ************" << endl;
    cout << "*************  1.開始演講比賽  *************" << endl;
    cout << "*************  2.查看往屆記錄  *************" << endl;
    cout << "*************  3.清空比賽記錄  *************" << endl;
    cout << "*************  0.退出比賽程序  *************" << endl;
    cout << "********************************************" << endl;
    cout << endl;
}

2.退出系統功能設計

我們首先設計的是退出系統功能的設計,相對於來說,這個功能是比較好實現的,只需要一個方法即可。可以將其聲明在比賽的流程類的頭文件中,然後在cpp文件中去實現這個方法,方法的簡單實現如下:

void SpeechManager::systemExit() {
    exit(0);
}

3.開始比賽功能設計

這一塊主要的功能是能夠分析清楚比賽的整體流程,按照項目的要求,來模擬整個比賽的流程:

可以分析如下:

首先是存在12名選手,然後隨機將其分爲2組,每組6個人(抽籤分組),完成了這個內容之後,那麼可以開始第一輪比賽,每個小組取前三名選手進入第二輪比賽,現在總共的就是存在6名選手,再次抽籤,進行第二輪比賽,然後等到第二輪比賽結束之後,記錄前三名選手的信息(編號,以及分數),保存到文件中。這個就是整體的流程,將上面的文字使用方法的流程體現出來如下:

// 比賽流程實現
void SpeechManager::startSpeech() {
    //第一輪比賽
    //1、抽籤

    //2、比賽

    //3、顯示晉級結果


    //第二輪比賽
    //1、抽籤

    //2、比賽

    //3、顯示最終結果

    //4、保存分數
}

在這個裏面,我們就是需要設計四個方法,分別是抽籤、比賽、顯示晉級結果、保存分數,有了整體的流程,那麼可以開始來實現單個方法。

3.1 抽籤

模擬抽籤的話,就是將選手的編號信息即可。我們可以採用vector來存儲選手,然後將vector shuffle即可。

void SpeechManager::speechDraw(){
    // 只是需要將選手的序號打亂即可
    cout << "第 << " << this->m_index << " >> 輪比賽選手正在抽籤"<<endl;
    cout << "---------------------" << endl;
    cout << "抽籤後演講順序如下:" << endl;
    if (this->m_index == 1){
        random_shuffle(v1.begin(), v1.end());
        for (vector<int>::iterator it = v1.begin(); it != v1.end(); it++){
            cout << *it << " ";
        }
        cout << endl;
    }
    else{
        random_shuffle(v2.begin(), v2.end());
        for (vector<int>::iterator it = v2.begin(); it != v2.end(); it++){
            cout << *it << " ";
        }
        cout << endl;
    }
    cout << "---------------------" << endl;
    cout << endl;
}
3.2 比賽

對於比賽,整體的情況是分爲兩輪的,但是我們可以設計一個小小的trick,使其不用每次都進行判斷,這裏我們採用一個新的vector來保存新的選手的消息,從而得到預先就進行了判斷,而不用重複分別寫第一輪比賽和第二輪比賽的內容。

比賽,對於本項目而言,就是給每個選手進行打分,然後對於第一輪,我們直接可以每個裁判的打分採用deque進行存儲,去掉最高分、最低分之後求平均就是每個選手的得分,然後將每個小組的前三名選手及其得分存儲起來,這即是第二輪參賽選手;對於第二輪選手,同樣的先進行抽籤,然後評委開始給每個選手進行打分,最後得到前三名即可。


// 開始比賽
void SpeechManager::speechContext() {

    multimap<double ,int,greater<int>> groupScore; //臨時容器,保存key分數 value 選手編號
    vector <int>v_Src;   //比賽的人員容器
    if (this->m_index == 1){
        v_Src = v1;
    }
    else{
        v_Src = v2;
    }
    int num = 0; //記錄人員數,6個爲1組

    // 直接給每個人員進行打分
    for(vector<int>::iterator it=v_Src.begin();it!=v_Src.end();it++){
        num++;
        //十個評委,分別打分
        deque<double> d;

        for(int i=0;i<10;i++){
            double score=0;
            score=(rand() % 401 + 600) / 10.f;  // 600 ~ 1000
            d.push_back(score);
        }

        //排序
        sort(d.begin(),d.end(),greater<double>());

        //去掉一個最高分以及一個最低分
        d.pop_front();
        d.pop_back();

        // 計算總和
        double sum=accumulate(d.begin(),d.end(),0.0f);

        // 計算均分
        double avg = sum / (double)d.size();

        this->m_Speaker[*it].m_score[this->m_index - 1]=avg;


        //6個人一組,用臨時容器保存
        groupScore.insert(make_pair(avg, *it));
        if (num % 6 == 0){

            cout << "第" << num / 6 << "小組比賽名次:" << endl;
            for (multimap<double, int, greater<int>>::iterator it = groupScore.begin(); it != groupScore.end(); it++)
            {
                cout << "編號: " << it->second << " 姓名: " << this->m_Speaker[it->second].m_name << " 成績: " << this->m_Speaker[it->second].m_score[this->m_index - 1] << endl;
            }

            int count = 0;
            //取前三名
            for (multimap<double, int, greater<int>>::iterator it = groupScore.begin(); it != groupScore.end() && count < 3; it++, count++){
                if (this->m_index == 1){
                    v2.push_back((*it).second);
                }
                else{
                    vVictory.push_back((*it).second);
                }
            }

            groupScore.clear();

            cout << endl;

        }
    }
    cout << "------------- 第" << this->m_index << "輪比賽完畢  ------------- " << endl;

}
3.3 顯示晉級結果

這裏的顯示,只是需要將容器中的內容打印出來即可。

void SpeechManager::showScore(){
    cout << "---------第" << this->m_index << "輪晉級選手信息如下:-----------" << endl;
    vector<int>v;
    if (this->m_index == 1){
        v = v2;
    }
    else{
        v = vVictory;
    }

    for (vector<int>::iterator it = v.begin(); it != v.end(); it++){
        cout << "選手編號:" << *it << " 姓名: " << m_Speaker[*it].m_name << " 得分: " << m_Speaker[*it].m_score[this->m_index - 1] << endl;
    }
    cout << endl;
}
3.4 保存分數

保存分數的話,主要是將其寫入到文件中,這裏只是需要記錄第二名比賽前三名的信息,在C++中,我們寫文件主要是包含以下五個步驟:

  1. 包含頭文件

    #include <fstream>

  2. 創建流對象

    ofstream ofs;

  3. 打開文件

    ofs.open(“文件路徑”,打開方式);

  4. 寫數據

    ofs << “寫入的數據”;

  5. 關閉文件

    ofs.close();

void SpeechManager::saveRecord() {
    fstream ofs;

    // 用輸出的方式打開文件  -- 寫文件
    ofs.open("../speech.csv", ios::out | ios::app);

    //將每個人數據寫入到文件中
    for (vector<int>::iterator it = vVictory.begin(); it != vVictory.end(); it++){
        ofs << *it << ","
            << m_Speaker[*it].m_score[1] << ",";
    }
    ofs << endl;

    //關閉文件
    ofs.close();

    cout << "記錄已經保存" << endl;
    this->fileIsEmpty= false;
}

4.查看記錄功能設計

查看記錄的功能主要是將內容從文件中讀取出來,這樣就是可以看到每一屆選手的前三名的成績了。即分析清楚之後,我們就可以看到這是一個簡單的讀文件的操作:


void SpeechManager::readRecord() {
    ifstream ifs;
    ifs.open("../speech.csv",ios::in);

    if (!ifs.is_open()){
        this->fileIsEmpty = true;
        cout << "文件不存在!" << endl;
        ifs.close();
        return;
    }

    // 先嚐試讀取一個字符,
    char ch;
    ifs>>ch;
    if(ifs.eof()){
        cout << "文件爲空!" << endl;
        this->fileIsEmpty = true;
        ifs.close();
        return;
    }

    //文件不爲空,將讀取的字符放回去
    ifs.putback(ch);

    string data;
    int index=0;

    while(ifs>>data) {
        vector<string> v;

        int pos = -1;
        int start = 0;

        while (true) {
            pos = data.find(",", start); //從0開始查找 ','
            if (pos == -1) {
                break; //找不到break返回
            }
            string tmp = data.substr(start, pos - start); //找到了,進行分割 參數1 起始位置,參數2 截取長度
            v.push_back(tmp);
            start = pos + 1;
        }
        this->m_Record.insert(make_pair(index, v));
        index++;
    }
    ifs.close();
}

5.清空記錄功能設計

清空記錄的話,主要是將文件中的所有的信息刪除即可。

void SpeechManager::clearRecord(){
    		//打開模式 ios::trunc 如果存在刪除文件並重新創建
        ofstream ofs("../speech.csv", ios::trunc);
        ofs.close();

        //初始化屬性
        this->initSpeech();

        //創建選手
        this->createSpeaker();

        //獲取往屆記錄
        this->readRecord();

        cout << "清空成功!" << endl;
}

至此,所有的小功能均已設計完畢。

當然,還有一些小bug的解決,在代碼中有詳細的體現哦.

歡迎到我的github上觀看完整內容

Reference

C++ STL
bilibili 項目課(網址沒找到,待補充)

總結

通過這個小項目,可以重溫一下stl相關的內容,熟悉流程。通過分析流程,更加能夠對語言的認識更加的深刻。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章