Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- C
- 프레임워크
- vector
- 메모리관리
- 옵저버 패턴
- material
- thread
- Multithread
- 멀티쓰레드
- sequential
- c++
- 디자인패턴
- MultiCore
- multi-core
- 옵저버
- 쓰레드
- random access
- 스마트포인터
- 게임공학과
- 한국산업기술대학교
- Atomic
- EFFECTIVE C++
- 복사생성자
- Unreal
- 유니크포인터
- stl
- observer pattern
- Design Pattern
- 멀티코어
- 멀티코어 프로그래밍
Archives
- Today
- Total
태크놀로지
[C++/STL] Ranking System 소개 및 구현 본문
프로젝트 소개
프로젝트 내용: 플레이어 랭킹 관리 프로그램 작성
프로그램 요구사항
- STL Container, Iterator, Algorithm을 사용한다.
- 플레이어는 다른 플레이어와 구분되는 고유ID를 갖는다.
- 플레이어는 챔피언스리그 / 떼탈출에서 얻은 점수를 멤버변수로 갖는다.
초기화
100,000 명의 플레이어를 생성한다. 랜덤엔진을 사용하여 플레이어의 점수를 생성한다.
게임진행 - 한 번 진행할 때마다 게임 한 시즌이 끝났다고 가정한다.
전체 100,000 플레이어 중 임의의 50,000 명의 플레이어가 떼탈출을 플레이한다. 또 다른 임의의 50,000 명의 플레이어가 챔피언스리그를 플레이한다.
떼탈출에서 획득한 점수는 0~2,905,887,026점 사이의 정규분포값을 갖도록 설정한다. 챔피언스리그에서 점수는 0~1,112,670,384점 사이의 정규분포값을 갖도록 설정한다.
플레이어가 얻은 점수가 이전 점수보다 높으면 높은 점수를 저장한다. 프로그램은 찾고자 하는 플레이어의 정보를 확인하여 존재하는 플레이어라면 떼탈출과 챔피언스리그에서 얻은 점수를 다른 플레이어의 점수와 비교하여
- 내 등수, 상위 몇 %인가, 내 점수는 얼마인가를 표시한다.
- 내 순위 기준으로 내 위와 아래 순위 플레이어의 id와 점수를 표시한다.
프로젝트 구현
랭킹 시스템 프로젝트를 구현하고, 컨테이너 별 성능을 비교하여 적합한 STL 컨테이너를 선택합니다.
▶ 랭킹시스템 프로젝트 특징 - 검색기능
- 한 번 진행할 때마다 게임 한 시즌이 끝났다고 가정한다.
- 내 정보를 알아야 하고, 내 순위 기준으로 위 아래 순위 플레이어 정보를 알아내야한다.
▶ 모든 플레이어들을 관리할 컨테이너 목록 (어뎁터 컨테이너는 제외하겠습니다.)
- 시퀀스 컨테이너
- vector
- deque
- list / Forward list
- 연관 컨테이너
- map / multi, unordered
- set / multi, unordered
▶ 성능분석
- 시퀀스 컨테이너 vs 연관 컨테이너
- 시퀀스 컨테이너가 더 빠르다면?
- Sequential access(순차 접근) vs Random access(비순차 접근)
▶ 구현내용
<파라미터>
- 플레이어 클래스
#pragma once
class Player
{
private:
string m_ID;
unsigned int m_Breakout; // 떼탈출 점수
unsigned int m_ChampionsLeague; // 챔피언스리그 점수
public:
string GetID() const;
void SetBreakoutScore(unsigned int score);
unsigned int GetBreakoutScore() const;
void SetChampionsLeagueScore(unsigned int score);
unsigned int GetChampionsLeagueScore() const;
public:
void UpdateBreakout(unsigned int breakout);
void UpdateChampionsLeague(unsigned int championsLeague);
public:
explicit Player(std::string id);
virtual ~Player();
};
#include "stdafx.h"
#include "Player.h"
Player::Player(std::string id) : m_ID(id),
m_Breakout(0), m_ChampionsLeague(0)
{
}
Player::~Player()
{
}
string Player::GetID() const
{
return m_ID;
}
void Player::SetBreakoutScore(unsigned int score)
{
m_Breakout = score;
}
unsigned int Player::GetBreakoutScore() const
{
return m_Breakout;
}
void Player::SetChampionsLeagueScore(unsigned int score)
{
m_ChampionsLeague = score;
}
unsigned int Player::GetChampionsLeagueScore() const
{
return m_ChampionsLeague;
}
void Player::UpdateBreakout(unsigned int breakout)
{
// 갱신된 점수가 기존점수보다 높을경우만 업데이트를한다.
if(breakout > m_Breakout)
m_Breakout = breakout;
}
void Player::UpdateChampionsLeague(unsigned int championsLeague)
{
// 갱신된 점수가 기존점수보다 높을경우만 업데이트를한다.
if (championsLeague > m_ChampionsLeague)
m_ChampionsLeague = championsLeague;
}
- 전역변수
#define VERSION_SEQUENCE 0xFF1
//#define VERSION_ASSOCIATE 0xFF2
constexpr int TOTAL_PLAYER_COUNT = 10'0000;
enum GAMETYPE {BREAKOUT = 0, CHAMPIONS_LEAGUE};
namespace SharedRandomValue
{
default_random_engine dre;
uniform_int_distribution<> randomNameLen(3, 7);
uniform_int_distribution<> randomName('a', 'z');
normal_distribution<double> breakoutDistribution(0.0f, 1.0f);
normal_distribution<double> championsLeagueDistribution(0.0f, 1.0f);
normal_distribution<double> chooseDistribution(0.0f, 1.0f);
}
// 전체플레이어를 담고있는 컨테이너
std::vector<Player*> g_AllPlayers;
// 플레이어가 존재하는지 여부를 알기위한 컨테이너
std::set<string> g_IsPlayer;
<유틸메서드>
- Util
namespace Util
{
double CalcNormalDistribScore(GAMETYPE gameType)
{
double score = 0;
if (gameType == GAMETYPE::BREAKOUT)
{
score = SharedRandomValue::breakoutDistribution(SharedRandomValue::dre);
score = clamp(score, -5.0, 5.0);
score += 5.0;
score *= 2905'8870'2.6;
}
else if (gameType == GAMETYPE::CHAMPIONS_LEAGUE)
{
score = SharedRandomValue::championsLeagueDistribution(SharedRandomValue::dre);
score = clamp(score, -5.0, 5.0);
score += 5.0;
score *= 1112'6703'8.4;
}
return score;
}
}
- GameUtil
namespace GameUtil
{
void CreatePlayers()
{
g_AllPlayers.resize(TOTAL_PLAYER_COUNT);
// 저장한 파일이 없다면 100,000 명의 플레이어를 생성한다.
for (int i = 0; i < TOTAL_PLAYER_COUNT; ++i)
{
// 이름생성
string id;
for (int i = 0; i < SharedRandomValue::randomNameLen(SharedRandomValue::dre); ++i) id += SharedRandomValue::randomName(SharedRandomValue::dre);
// 정규분포값 생성 (breakout, championsLeague)
double breakout_Distribution = Util::CalcNormalDistribScore(GAMETYPE::BREAKOUT);
double championsLeague_Distribution = Util::CalcNormalDistribScore(GAMETYPE::CHAMPIONS_LEAGUE);
Player* player = new Player(id);
player->SetBreakoutScore(breakout_Distribution);
player->SetChampionsLeagueScore(championsLeague_Distribution);
// 모든 플레이어를 담는 vector
g_AllPlayers[i] = player;
// 플레이어 존재여부를 위한 컨테이너 set
g_IsPlayer.emplace(id);
}
}
void ShowRanking(const int index, GAMETYPE gameType)
{
if (index < 0) return;
#ifdef VERSION_SEQUENCE
if (gameType == GAMETYPE::BREAKOUT)
cout << g_AllPlayers[index]->GetID() << " " << index << "등 " << "상위 " << (index) * 100.f / TOTAL_PLAYER_COUNT << "%" << " 점수 " << g_AllPlayers[index]->GetBreakoutScore() << endl;
else
cout << g_AllPlayers[index]->GetID() << " " << index << "등 " << "상위 " << (index) * 100.f / TOTAL_PLAYER_COUNT << "%" << " 점수 " << g_AllPlayers[index]->GetChampionsLeagueScore() << endl;
#endif
}
}
<프로젝트 프로세스>
- main
int main()
{
/*[초기화]*/
// 저장한 파일이 없다면 100,000 명의 플레이어를 생성한다.
// 랜덤엔진을 사용하여 플레이어의 점수를 생성한다. (normal_distribution을 사용할 것)
// 닉네임 받아오기
GameUtil::CreatePlayers();
cout << "My Player ID: " << g_AllPlayers[0]->GetID() << endl;
// While loop
/*[게임진행] - 한 번 진행할 때마다 게임 한 시즌이 끝났다고 가정한다.*/
while (true)
{
string id;
cout << "ID를 입력하세요: ";
cin >> id;
// 플레이어 여부 체크
if (g_IsPlayer.count(id) == 0) {
cout << "존재하지 않는 ID입니다." << endl << endl;
}
else Process(id);
}
}
- Process
void Process(string id)
{
cout << "------------------------------------------------------------------------" << endl;
// 스코어 정규분포값 구하기
double breakout_Distribution = Util::CalcNormalDistribScore(GAMETYPE::BREAKOUT);
double championsLeague_Distribution = Util::CalcNormalDistribScore(GAMETYPE::CHAMPIONS_LEAGUE);
// 점수 업데이트
// 임의의 50, 000 명의 플레이어가 떼탈출을 플레이한다.
// 또 다른 임의의 50, 000 명의 플레이어가 챔피언스리그를 플레이한다.
for (auto& p : g_AllPlayers)
{
if (SharedRandomValue::chooseDistribution(SharedRandomValue::dre) < 0)
p->UpdateBreakout(breakout_Distribution);
else
p->UpdateChampionsLeague(championsLeague_Distribution);
}
// chrono 시간측정 시작
auto start_t = high_resolution_clock::now();
#ifdef VERSION_SEQUENCE
/* Breakout Score */
// Breakout 기준으로 내림차순 정렬
// TimeComplex : quicksort (nlogN ~ n^2)
sort(g_AllPlayers.begin(), g_AllPlayers.end(), [](const Player* player1, const Player* player2) {
return player1->GetBreakoutScore() > player2->GetBreakoutScore();
});
// Breakout Score 검색
// TimeComplex : 순차탐색(N)
auto iter = find_if(g_AllPlayers.begin(), g_AllPlayers.end(), [&](const Player* player1) {
return player1->GetID() == id;
});
int index = iter - g_AllPlayers.begin();
// ShowScreen
cout << "[ 때탈출 ]" << endl;
GameUtil::ShowRanking( index - 1, GAMETYPE::BREAKOUT);
GameUtil::ShowRanking(index, GAMETYPE::BREAKOUT);
GameUtil::ShowRanking(index + 1, GAMETYPE::BREAKOUT);
cout << endl;
//////////////////////////////////////////////////////////
/* Champions-League Score */
sort(g_AllPlayers.begin(), g_AllPlayers.end(), [](const Player* player1, const Player* player2) {
return player1->GetChampionsLeagueScore() > player2->GetChampionsLeagueScore();
});
iter = find_if(g_AllPlayers.begin(), g_AllPlayers.end(), [&](const Player* player1) {
return player1->GetID() == id;
});
index = iter - g_AllPlayers.begin();
cout << "[ 챔피언스리그 ]" << endl;
GameUtil::ShowRanking(index - 1,GAMETYPE::CHAMPIONS_LEAGUE);
GameUtil::ShowRanking(index, GAMETYPE::CHAMPIONS_LEAGUE);
GameUtil::ShowRanking(index + 1, GAMETYPE::CHAMPIONS_LEAGUE);
cout << endl;
#elif VERSION_ASSOCIATE
// 정렬을 위해 각 분야마다 multimap 생성
map<int, Player*> breakoutMap;
map<int, Player*> championsLeagueMap;
for (auto& p : g_AllPlayers) {
breakoutMap.emplace(p->GetBreakoutScore(), p);
championsLeagueMap.emplace(p->GetChampionsLeagueScore(), p);
}
map<int, Player*>::iterator breakoutIter;
for (auto iter = breakoutMap.begin(); iter != breakoutMap.end(); ++iter)
{
if (iter->second->GetID() == id) {
breakoutIter = iter;
break;
}
}
auto previter = breakoutIter;
auto preiter = breakoutIter;
cout << "[ 때탈출 ]" << endl;
cout << (--previter)->second->GetID() << " 점수" << (--previter)->first << endl;
cout << breakoutIter->second->GetID() << " 점수" << breakoutIter->first << endl;
cout << (++preiter)->second->GetID() << " 점수" << (++preiter)->first << endl;
cout << endl;
map<int, Player*>::iterator champMap;
for (auto iter = championsLeagueMap.begin(); iter != championsLeagueMap.end(); ++iter)
{
if (iter->second->GetID() == id) {
champMap = iter;
break;
}
}
previter = champMap;
preiter = champMap;
cout << "[ 챔피언스리그 ]" << endl;
cout << (--previter)->second->GetID() << " 점수" << (--previter)->first << endl;
cout << champMap->second->GetID() << " 점수" << champMap->first << endl;
cout << (++preiter)->second->GetID() << " 점수" << (++preiter)->first << endl;
cout << endl;
#endif
auto end_t = high_resolution_clock::now();
auto exec_time = end_t - start_t;
cout << "Exec Time = " << duration_cast<milliseconds>(exec_time).count() << "ms" << endl;
cout << "------------------------------------------------------------------------" << endl;
}
프로젝트 출처
한국산업기술대학교 게임공학부 윤정현교수님 STL 수업 과제
'C++' 카테고리의 다른 글
[Effective C++] 왜 const를 사용해야하는가 (0) | 2020.11.18 |
---|---|
[C++/STL] Ranking System 분석 (0) | 2020.09.25 |
[C++] vector 메모리 관리 구조 분석 (0) | 2020.08.07 |
[C++] unique_ptr 만들기 - 스마트 포인터가 제공하는 메서드와 연산자 구현 (+ reset / release 차이점 이해) (0) | 2020.08.07 |
[C++] unique_ptr 만들기 - 생성자와 대입연산자 구현하기 (디폴트, 복사, 이동) (1) | 2020.05.31 |