[C++] 263 実行ファイルからappファイルを作成 fltk-config

[M1 Mac, Monterey 12.6.3, clang 13.0.0, SDL 2.26.2, ChatGPT Plus, NO IDE]

FLTKのfltk-configコマンドで実行ファイルからappファイルを作成できます。SDLアプリでも使えました。

appファイルの中に実行ファイルが格納されており、icnsファイルやInfo.plistをコピーすると完成になります。

fltk-configではなく正式な方法でappファイルを作成できるようにしたいところです。

# コンパイラ設定他
COMPILER = clang++
DEBUG = -g

# フラグ設定
CPPFLAGS = $(shell pkg-config sdl2 --cflags --libs) -std=c++11
LDFLAGS =

# includeパス(-I)
INCLUDE = -I./include -I/opt/homebrew/Cellar/sdl2/2.26.2/include \
		-I/opt/homebrew/Cellar/sdl2_ttf/2.20.2/include \
		-I/Volumes/DATA_m1/code/cpp/mylib/include \

# ライブラリ(-l)
LIBRARY0 = -lsdl2 -lsdl2_ttf

# ライブラリパス(-L)
LIBRARY = -L/opt/homebrew/Cellar/sdl2/2.26.2/lib \
		-L/opt/homebrew/Cellar/sdl2_ttf/2.20.2/lib \

# ソースファイル
SRCDIR = ./src
SRCS = $(shell find $(SRCDIR) -type f)

# オブジェクトファイル
OBJDIR = ./obj
OBJS = $(addprefix $(OBJDIR), $(patsubst ./src/%.cpp,/%.o,$(SRCS)))

# 実行ファイル
TARGETDIR = ./bin
TARGET = TetrisDX

# ICNSファイル
ICON = ./images/TetrisDX.icns
	
# cppファイルからoファイル作成 $<:依存ファイル
$(OBJDIR)/%.o: $(SRCDIR)/%.cpp
	$(COMPILER) $(CPPFLAGS) $(INCLUDE) $(DEBUG) -o $@ -c $<

# appファイル作成関連(FLTK以外のアプリにも使える)
POSTBUILD  = fltk-config --post

# oファイルから実行ファイル作成
$(TARGET):$(OBJS)
	$(COMPILER) -o $(TARGETDIR)/$@ $(OBJS) $(LIBRARY0) $(LIBRARY) $(LDFLAGS)
	cp $(TARGETDIR)/$(TARGET) $(TARGET)
	$(POSTBUILD) $(TARGET)
	mkdir $(TARGET).app/Contents/Resources
	cp ./images/$(TARGET).icns $(TARGET).app/Contents/Resources
	plutil -insert 'CFBundleIconFile' -string $(TARGET).icns $(TARGET).app/Contents/Info.plist
	rm -f $(TARGET)

# 全ソース・コンパイル&ビルド
.PHONY:all
all: clean $(OBJS) $(TARGET)

# oファイル・実行ファイル削除
.PHONY:clean
clean:
	rm -rf $(OBJS) $(TARGETDIR)/$(TARGET)

[C++] 262 SDL : テトリス落下スピード選択 その2 実装

[M1 Mac, Monterey 12.6.3, clang 13.0.0, SDL 2.26.2, ChatGPT Plus, NO IDE]

スタート画面でテトリスの落下速度を選べるようにしました。今の私のゲームスキルでSlowは20分、Normalは10分、Fastは5分程度でケリがつくように調整しました。

PS4コントローラーのR1ボタンかマウスクリックで選択できます。

selectButton関数の叩きはChatGPTが書いてくれました。

void App::Run()
{
	Uint32 lastTimeMs = SDL_GetTicks();
	auto lastTime = std::chrono::high_resolution_clock::now();

	bool bDone = false;
	while( !bDone )
	{
		GameInput gameInput = {};
		SDL_JoystickUpdate();

		// respond to events
		SDL_Event event;
		while( SDL_PollEvent( &event ) )
		{
			if(event.type == SDL_QUIT)
			{
				bDone = true;
			}

			// 落下速度ボタン選択(マウスクリック)
			if(event.type == SDL_MOUSEBUTTONDOWN)
			{
				mouseX = event.motion.x;
				mouseY = event.motion.y;
				SDL_Delay(10);
				selectButton(slowButton, normalButton, fastButton, mouseX, mouseY);
			}

			if(event.type == SDL_JOYBUTTONDOWN)
			{
				switch (event.jbutton.button){
					case 2:{	// □ボタン:スタート
						gameInput.bStart = true;
						start = std::chrono::steady_clock::now();
						scores = {};
						dates = {};
						fullDates = {};
						numAllScores = {};
						rank = 0;
						DrawPlayingCount = 0;
						GeneratorCount = 0;
						arr = {};
						break;
					}
					case 13:{	// 左ボタン
						gameInput.bMoveLeft = true;
						break;
					}
					case 14:{	// 右ボタン
						gameInput.bMoveRight = true;
						break;
					}
					case 1:{	// ○ボタン:反時計回り回転
						gameInput.bRotateClockwise = true;
						break;
					}
					case 0:{	// ×ボタン:時計回り回転
						gameInput.bRotateAnticlockwise = true;
						break;
					}
					case 12:{	// 下ボタン:一気に落下
						gameInput.bHardDrop = true;
						break;
					}
					case 11:{	// 上ボタン:徐々に落下
						gameInput.bSoftDrop = true;
						break;
					}
					case 10:{	// R1ボタン 落下速度ボタン選択
						buttonCount++;
						if (buttonCount % 3 == 1){
							fastButton.selected = true;
							normalButton.selected = false;
							s_initialFramesPerFallStep = 24;

						} else if (buttonCount % 3 == 2){
							slowButton.selected = true;
							fastButton.selected = false;
							s_initialFramesPerFallStep = 48;

						} else {
							normalButton.selected = true;
							slowButton.selected = false;
							s_initialFramesPerFallStep = 32;
						}
						break;
					}
					default:{
						cout << "default" << endl;
						break;
					}
				}
			}
		}
		
		// FPS算出のために1フレーム時間を計算
		Uint32 currentTimeMs = SDL_GetTicks();
		Uint32 deltaTimeMs = currentTimeMs - lastTimeMs;
		lastTimeMs = currentTimeMs;
		HP_UNUSED( deltaTimeMs );
		
		auto currentTime = std::chrono::high_resolution_clock::now();
		auto deltaTime = currentTime - lastTime;
		std::chrono::microseconds deltaTimeMicroseconds = std::chrono::duration_cast<std::chrono::microseconds>(deltaTime);
		float deltaTimeSeconds = 0.000001f * (float)deltaTimeMicroseconds.count();
		lastTime = currentTime;

		mGame->Update(gameInput, deltaTimeSeconds );

		mRenderer->Clear();
		mGame->Draw(*mRenderer );
		mRenderer->Present();
	}
}
void selectButton(Button& slow, Button& normal, Button& fast, const int mouseX, const int mouseY) {
    slow.selected = false;
    normal.selected = false;
    fast.selected = false;

    if (mouseX >= slow.rect.x && mouseX <= slow.rect.x + slow.rect.w &&
        mouseY >= slow.rect.y && mouseY <= slow.rect.y + slow.rect.h) {
        slow.selected = true;
        s_initialFramesPerFallStep = 48;
        cout << "slow clicked" << endl;

    } else if (mouseX >= normal.rect.x && mouseX <= normal.rect.x + normal.rect.w &&
        mouseY >= normal.rect.y && mouseY <= normal.rect.y + normal.rect.h) {
        normal.selected = true;
        s_initialFramesPerFallStep = 32;
        cout << "normal clicked" << endl;

    } else if (mouseX >= fast.rect.x && mouseX <= fast.rect.x + fast.rect.w &&
        mouseY >= fast.rect.y && mouseY <= fast.rect.y + fast.rect.h) {
        fast.selected = true;
        s_initialFramesPerFallStep = 24;
        cout << "fast clicked" << endl;
    }
}

[C++] 261 SDL : テトリス落下スピード選択 その1 ChatGPT

[M1 Mac, Monterey 12.6.3, clang 13.0.0, SDL 2.26.2, ChatGPT Plus, NO IDE]

これまで開発してきたテトリスはベーシック版として、多機能版を別に開発することにしました。

まずはテトロミノの落下スピードを調整できるようにします。ボタンを3つレンダリングしています。

ChatGPTにヘッダファイルを見せた上でコードの作成方法を聞いてみました。

Q: <Game.hは省略> 以上のGame.hを踏まえて落下スピードを3段階に設定するにはどうしたらよいですか?

Tetrisの落下スピードを3段階に設定する場合は、Gameクラス内に落下スピードを管理する変数を追加し、GameInput構造体にボタン入力を追加して、それに応じて落下スピードを変更するようにUpdatePlaying関数を修正する必要があります。

以下は、例としてUpdatePlaying関数を修正したコードです。ここでは、bSlow、bNormal、bFastの3つのフラグ変数をGameInput構造体に追加して、それぞれがtrueの場合に対応する落下スピードを設定しています。

void Game::UpdatePlaying(const GameInput& gameInput) {
    if (gameInput.bPause) {
        m_gameState = kGameState_TitleScreen;
        return;
    }

    // 落下スピードの設定
    int framesPerFallStep;
    if (gameInput.bSlow) {
        framesPerFallStep = 48;
    } else if (gameInput.bNormal) {
        framesPerFallStep = 32;
    } else {
        framesPerFallStep = 16;
    }

    // 落下フレーム数の計算
    m_framesUntilFall -= 1;
    if (m_framesUntilFall == 0) {
        m_framesUntilFall = framesPerFallStep;
        m_activeTetromino.m_pos.y += 1;
        if (CheckTetrominoCollision(m_activeTetromino)) {
            // 衝突した場合、テトロミノをフィールドに追加する
            AddTetronimoToField(m_field, m_activeTetromino);
            // 行を消去する
            int numLinesCleared = ClearFullLines(m_field);
            m_numLinesCleared += numLinesCleared;
            m_level = m_numLinesCleared / 10;
            // 新しいテトロミノをスポーンする
            if (!SpawnTetromino()) {
                m_gameState = kGameState_GameOver;
                return;
            }
        }
    }

    // 入力に応じた処理
    if (gameInput.bMoveLeft) {
        if (!CheckTetrominoCollision(m_activeTetromino, -1, 0)) {
            m_activeTetromino.m_pos.x -= 1;
        }
    }
    if (gameInput.bMoveRight) {
        if (!CheckTetrominoCollision(m_activeTetromino, 1, 0)) {
            m_activeTetromino.m_pos.x += 1;
        }
    }
    if (gameInput.bRotateClockwise) {
        if (!CheckTetrominoCollision(m_activeTetromino, 0, 0, 1)) {
            m_activeTetromino.m_rotation = (m_activeTetromino.m_rotation + 1) % Tetromino::kNumRotations;
        }
    }
    if (gameInput.bRotateAnticlockwise) {
        if (!CheckTetrominoCollision(m_activeTetromino, 0, 0, -1)) {
            m_activeTetromino.m_rotation = (m_activeTetromino.m_rotation + Tetrom

[C++] 260 乱数生成方法 randomヘッダ

[M1 Mac, Monterey 12.6.3, clang 13.0.0, SDL 2.26.2, ChatGPT Plus, NO IDE]

テトリスC++版ではMacOSのstdlib.hにあるrand関数をこれまで使っていましたが、偏りがひどく乱数生成器として用を成さないため、ChatGPTに教えてもらった<random>ヘッダに変更しました。

#include <random>

int TetroTypeGenerator()
{	
	int tetroType;

	if (GeneratorCount == 0){
		arr = {0, 1, 2, 3, 4, 5, 6};
	}

	while(1){
		// 乱数生成器を初期化する
		std::random_device seed_gen;
		std::mt19937 engine(seed_gen());
		
		// 0から6の7つの整数から一つランダムに選択する
		std::uniform_int_distribution<int> dist(0, 6);
		int tetroType = dist(engine);
		// tetroType = rand() % kNumTetrominoTypes; // 偏りがひどいので不採用
		cout << "tetroType生成器: " << tetroType << endl;

		if (arr.size() == 0){
			arr = {0, 1, 2, 3, 4, 5, 6};
			cout << "テトロミノ配列が空になったのでリセットしました" << endl;
		} else if (arr.size() == 1){
			tetroType = arr.front();
			arr = {};

			cout << "tetroType残り1: " << to_string(tetroType) << endl;
			GetScoreAndDate();
			makeRanking();
			GeneratorCount++;
			return tetroType;
		}

		for (auto it = arr.begin(); it != arr.end(); ) {
			if (*it == tetroType) {
				it = arr.erase(it);

				cout << "tetroType: " << to_string(tetroType) << endl;
				GetScoreAndDate();
				makeRanking();

				GeneratorCount++;
				return tetroType;

			} else {
				it++;
			}
		}
	}
}

ChatGPT
Q: MacOSのstdlib.hにあるrand関数の生成する乱数が偏っています。なぜですか?

MacOSのstdlib.hにあるrand関数は、線形合同法に基づいた乱数生成アルゴリズムを使用しています。このアルゴリズムは、周期が短く、生成される乱数の分布が均等ではないため、偏った乱数が生成される可能性があります。

具体的には、rand関数は0からRAND_MAXまでの範囲の整数を生成しますが、生成される乱数の分布が均等でないため、特定の値がより頻繁に生成される傾向があります。また、線形合同法による乱数生成アルゴリズムは、初期値に依存するため、同じ初期値を使用する場合は、同じ乱数の系列が生成されます。

そのため、rand関数が生成する乱数は、科学的なシミュレーションや暗号学的な目的などのように、高品質な乱数が必要な場合には適していません。代わりに、乱数生成器として、より高品質なアルゴリズムを使用することが推奨されます。たとえば、C++11以降では、<random>ヘッダーに、様々な種類の乱数生成器が用意されています。

[C++] 259 SDL : 暫定順位をカラー化 Tetris

[M1 Mac, Monterey 12.6.3, clang 13.0.0, SDL 2.26.2, ChatGPT Plus, NO IDE]

テトリスC++版のプレイ中に暫定順位を表示するのに合わせて、ランクインしている場合はカラー化してリアルタイムで表示するようにしました。

for文を使ってレンダリングする場合は、バイト数が一致するようにしないと表示がおかしくなります。今回は6〜9位と10位(1バイト多い)を別扱いにしています。

これで基本的な機能はほぼ実装したでしょうか。

void Game::DrawPlaying(Renderer& renderer)
{
<中略>

for (int i = 0 ; i < 5 ; i++){
		snprintf(text, sizeof(text), "High score %d: %s %s", i+1, to_string(scores[i]).c_str(), dates[i].c_str());
		if (fullDates[i] != startTime){
			renderer.DrawText(text, 20, 170 + 40*i, mFont, 0xffffffff );
		} else {
			renderer.DrawText( text, 20, 170 + 40*i, mFont, 0xFF4500ff );
		}
}

for (int i = 5 ; i < 9 ; i++){
		snprintf(text, sizeof(text), "High score  %d: %s %s", i+1, to_string(scores[i]).c_str(), dates[i].c_str());
		if (fullDates[i] != startTime){
			renderer.DrawText(text, 100, 370 + 30*(i-5), mFont2, 0xffffffff );
		} else {
			renderer.DrawText(text, 100, 370 + 30*(i-5), mFont2, 0xFF4500ff );
		}
}

snprintf(text, sizeof(text), "High score 10: %s %s", to_string(scores[9]).c_str(), dates[9].c_str());
if (fullDates[9] != startTime){
		renderer.DrawText(text, 100, 370 + 30*4, mFont2, 0xffffffff );
} else {
		renderer.DrawText(text, 100, 370 + 30*4, mFont2, 0xFF4500ff );
}

<以下略>

[C++] 258 SDL : 暫定順位を表示 Tetris

[M1 Mac, Monterey 12.6.3, clang 13.0.0, SDL 2.26.2, ChatGPT Plus, NO IDE]

テトリスをプレイ中に暫定順位(画面左下のCurrent Rank)を表示するようにしました。

これでベストテン入りにどれくらい近づいているかが分かるようになります。

ゲームアプリでは時間軸というファクターが加わり繰り返し演算し続けるため、これに巻き込まれないように計算しないと変数が知らぬ間に加算されたりします。

C++版の完成度が上がってきたので、そろそろiOS版の製作に着手します。

紫テトロミノの回転が間に合っていたら単独2位だった
int m_score;
int numAllScores;
int rank;
vector<int> scores;
vector<int> allScores;
vector<string> dates;
vector<string> times;

struct Entry {
    int score;
    string date;
	string playTime;
};

void makeRanking(){
	// 暫定順位算出
	allScores.push_back(m_score);
	sort(allScores.begin(), allScores.end(), std::greater<int>());
	numAllScores = allScores.size();

	for (int i = 0; i < allScores.size(); ++i) {
		if (allScores[i] == m_score) {
			rank = i + 1;
			printf("現在順位: %d / %d\n", rank, numAllScores);
			break;
		}
		++rank;
	}

	allScores.pop_back();
}

int TetroTypeGenerator()
{	
	int tetroType;

	if (GeneratorCount == 0){
		arr = {0, 1, 2, 3, 4, 5, 6};
	}

	while(1){
		tetroType = rand() % kNumTetrominoTypes;

		if (arr.size() == 0){
			arr = {0, 1, 2, 3, 4, 5, 6};
			cout << "テトロミノ配列が空になったのでリセットしました" << endl;
		} else if (arr.size() == 1){
			tetroType = arr.front();
			arr = {};

			cout << "tetroType残り1: " << to_string(tetroType) << endl;
			makeRanking();

			GeneratorCount++;
			return tetroType;
		}

		for (auto it = arr.begin(); it != arr.end(); ) {
			if (*it == tetroType) {
				it = arr.erase(it);

				cout << "tetroType: " << to_string(tetroType) << endl;
				makeRanking();

				GeneratorCount++;
				return tetroType;

			} else {
				it++;
			}
		}
	}
}

int GetScoreAndDate() {
	vector<Entry> entries = ReadCSV(csvFile);
	allScores = {};

    // スコアで降順ソート
    sort(entries.begin(), entries.end(),
              [](const Entry& a, const Entry& b) { return a.score > b.score; });

	// 全スコアのvector作成
	for (const auto& entry : entries) {
		allScores.push_back(entry.score);
		// cout << entry.score << " : " << entry.date << " : " << entry.playTime << std::endl;
	}

    // 上位10件を取得して、スコアと日付をvectorに格納
    for (int i = 0; i < std::min(10, static_cast<int>(entries.size())); ++i) {
        scores.push_back(entries[i].score);
        string date = entries[i].date;
		replace(date.begin(), date.end(), '-', '/');
		dates.push_back(date.substr(5, 5));
    }

    return 0;
}

void Game::DrawPlaying( Renderer& renderer )
{
<中略>

snprintf( text, sizeof(text), "Current Rank: %s / %s", to_string(rank).c_str(), to_string(numAllScores).c_str());
renderer.DrawText( text, 20, 530, mFont, 0xffffffff);

<以下略>
}

[C++] 257 SDL : フォントサイズを変えてレンダリング Tetris

[M1 Mac, Monterey 12.6.3, clang 13.0.0, SDL 2.26.2, ChatGPT Plus, NO IDE]

テトリスSDL版のベストスコア6〜10位についてフォントサイズを小さくして表示できるようDrawText関数にフォントの引数を追加しました。

TTF_Font*	mFont;
TTF_Font*	mFont2;

Renderer::Renderer内
int defaultFontSize = 32;
int smallFontSize = 24;
mFont = TTF_OpenFont( "clacon2.ttf", defaultFontSize );
mFont2 = TTF_OpenFont( "clacon2.ttf", smallFontSize );

void Renderer::DrawText( const char* text, int x, int y, TTF_Font* font, uint32_t rgba /*= 0xffffffff */ )
{
	SDL_assert( text );
	SDL_Color color = MakeSDL_Colour( rgba );
	SDL_Surface* pSurface = TTF_RenderUTF8_Solid(font, text, color);
	
	SDL_Texture* pTexture = SDL_CreateTextureFromSurface( m_pSdlRenderer, pSurface );
	int width, height;
	SDL_QueryTexture(pTexture, NULL, NULL, &width, &height);
	SDL_Rect dstRect = { x, y, width, height };
	SDL_RenderCopy( m_pSdlRenderer, pTexture, nullptr, &dstRect );
	SDL_DestroyTexture( pTexture );
	SDL_FreeSurface( pSurface );
}

[C++] 256 SDL : テトリス画面にベストスコア5つを表示 その4 CSVファイル更新

[M1 Mac, Monterey 12.6.3, clang 13.0.0, SDL 2.26.2, ChatGPT Plus, NO IDE]

テトリスのプレイ記録をCSVファイルに保存し、起動時・リスタート時にスコアベスト5を表示するようにしました。XMLでの苦労とは打って変わってのイージーモードでした。

これで最低限の機能を実装しました。ホールド機能などのワールドルールに準拠するかどうかはこれから考えます。

ネット麻雀の待ち牌表示やツモ切り表示のようなヌルい機能はいらない派なので多分実装しないでしょう。

struct Entry {
    int score;
    string date;
    string playtime;
};

vector<Entry> ReadCSV(const string& filename) {
    vector<Entry> entries;

    std::ifstream infile(filename);
    if (!infile) {
        cerr << "Failed to open file: " << filename << endl;
        return entries;
    }

	std::string line;
    std::getline(infile, line); // ヘッダ行を読み飛ばす
    while (std::getline(infile, line)) {
        std::istringstream iss(line);
        std::string score_str, date, playtime;
        std::getline(std::getline(iss, score_str, ','), date, ',');
        std::getline(iss, playtime, ',');
        int score = std::stoi(score_str);
        entries.push_back({score, date, playtime});
    }

    return entries;
}

int GetScoreAndDate() {
	vector<Entry> entries = ReadCSV(csvFile);

    // スコアで降順ソート
    sort(entries.begin(), entries.end(),
              [](const Entry& a, const Entry& b) { return a.score > b.score; });

    // 上位5件を取得して、スコアと日付をvectorに格納
    for (int i = 0; i < std::min(5, static_cast<int>(entries.size())); ++i) {
        scores.push_back(entries[i].score);
        string date = entries[i].date;
		replace(date.begin(), date.end(), '-', '/');
		dates.push_back(date.substr(5, 5));
    }

    return 0;
}

void CopyFile(const std::string& src_file, const std::string& dst_file) {
    std::ifstream src(src_file, std::ios::binary);
    std::ofstream dst(dst_file, std::ios::binary);
    dst << src.rdbuf();
}

void AddDataToCSV()
{
	std::ofstream outfile(csvFile, std::ios_base::app); // ファイルを追記モードで開く
    if (!outfile) {
        cerr << "Failed to open file: " << csvFile << endl;
        return;
    }

    outfile << m_score << "," << startTime << "," << elapsedTime << endl;

	// バックアップを作成する
    if (fs::exists(csvFile)) {
        CopyFile(csvFile, csvFileBackup);
    }
}

[C++] 255 SDL : テトリス画面にベストスコア5つを表示 その3 CSVファイル読み込み ChatGPT

[M1 Mac, Monterey 12.6.3, clang 13.0.0, SDL 2.26.2, ChatGPT Plus, NO IDE]

テトリスのプレイ記録をCSVファイルにまとめ、起動時にこれを読み込んでスコアのベスト5を表示させるようにします。

まずテスト用のCSVファイルについて、これを読み込んで必要なデータをvectorにまとめるコードをChatGPTに書いてもらいました。

内容を検証したところ正常に動作して、最後にvectorの内容を出力してくれるという120点の出来でした。ハマると素晴らしいコードを書いてくれます。

Q:
score,date,playtime
0,2020-03-01 14:20:30,25:00
800,2020-03-03 19:10:45,20:30
500,2020-03-07 08:15:20,15:00
300,2020-03-01 15:22:00,12:30
20,2020-02-20 23:55:09,10:00
1000,2020-02-07 23:55:09,10:00 
C++でこのcsvファイルを読み込み、scoreで降順ソートして上位5件のscoreと月/日をそれぞれvector<int>,vector<string>にして下さい。

以下は、与えられたCSVファイルを読み込み、スコアで降順ソートして上位5件のスコアと日付(月/日)をそれぞれvector<int>とvector<string>に格納するサンプルコードです。

#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <algorithm>

struct Entry {
    int score;
    std::string date;
    std::string playtime;
};

// CSVファイルを読み込み、各行をEntry構造体に変換してvectorに格納する
std::vector<Entry> ReadCSV(const std::string& filename) {
    std::vector<Entry> entries;

    std::ifstream infile(filename);
    if (!infile) {
        std::cerr << "Failed to open file: " << filename << std::endl;
        return entries;
    }

    std::string line;
    std::getline(infile, line); // ヘッダ行を読み飛ばす
    while (std::getline(infile, line)) {
        std::istringstream iss(line);
        std::string score_str, date, playtime;
        std::getline(std::getline(iss, score_str, ','), date, ',');
        std::getline(iss, playtime, ',');
        int score = std::stoi(score_str);
        entries.push_back({score, date, playtime});
    }

    return entries;
}

int main() {
    std::vector<Entry> entries = ReadCSV("scores.csv");

    // スコアで降順ソート
    std::sort(entries.begin(), entries.end(),
              [](const Entry& a, const Entry& b) { return a.score > b.score; });

    // 上位5件を取得して、スコアと日付をvectorに格納
    std::vector<int> top_scores;
    std::vector<std::string> dates;
    for (int i = 0; i < std::min(5, static_cast<int>(entries.size())); ++i) {
        top_scores.push_back(entries[i].score);
        std::string month_day = entries[i].date.substr(5, 5);
        dates.push_back(month_day);
    }

    // 結果を出力
    std::cout << "Top 5 scores:" << std::endl;
    for (int i = 0; i < static_cast<int>(top_scores.size()); ++i) {
        std::cout << top_scores[i] << ", " << dates[i] << std::endl;
    }

    return 0;
}

上記のコードを実行すると、出力結果が表示されます。

Top 5 scores:
1000, 02-07
800, 03-03
500, 03-07
300, 03-01
20, 02-20