[C++] 271 ChatGPT APIをC++で使ってみる

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

本日2023年3月2日からChatGPT APIが公開されたので早速使ってみました。

C++でChatGPTツールを製作し、ChatGPT Plusは解約して格安で使ってやろうという算段です。

#include <cppstd.h> // 自製ヘッダ
#include <curl/curl.h>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

// Utility function to write response data to a string
size_t writeCallback(char* buf, size_t size, size_t nmemb, void* up) {
    // Append the response data to the string
    ((string*)up)->append((char*)buf, size * nmemb);
    return size * nmemb;
}

json chatWithOpenAI(const string& question) {
    // Set up the request parameters
    string url = "https://api.openai.com/v1/chat/completions";
    string apiKey = getenv("CHATGPT_API_KEY");
    string authHeader = "Authorization: Bearer " + apiKey;

    curl_slist* headers = nullptr;
    headers = curl_slist_append(headers, authHeader.c_str());
    headers = curl_slist_append(headers, "Content-Type: application/json");

    string requestData = R"({"model":"gpt-3.5-turbo", "messages":[{"role":"user","content":")" + question + R"("}], "temperature":0.0})";
    string responseData;

    cout << "requestData: " << requestData << endl;

    // Set up a curl session
    CURL* curl = curl_easy_init();
    if (curl) {
        // Set the request options
        curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, requestData.c_str());
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseData);

        // Send the request
        CURLcode res = curl_easy_perform(curl);
        if (res != CURLE_OK) {
            cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << endl;
        }

        // Clean up
        curl_easy_cleanup(curl);

    } else {
        cerr << "curl_easy_init() failed" << endl;
    }

    // Parse the response data and extract the chat response
    json responseJson = json::parse(responseData);

    return responseJson;
}

int main() {
    string question = "iOSアプリをObjective-CやSwift以外の言語で作れますか";
    json response = chatWithOpenAI(question);
    cout << response.dump() << endl;
    return 0;
}
{"choices":[{"finish_reason":"stop","index":0,"message":
{"content":"\n\niOSアプリを作るためには、Appleが提供するiOS SDKを使用する必要があります。
iOS SDKはObjective-CやSwiftで書かれていますが、他の言語で書かれたアプリを作ることも可能です。\n\n
例えば、React NativeやXamarinなどのクロスプラットフォーム開発ツールを使用することで、
JavaScriptやC#などの言語でiOSアプリを作ることができます。
ただし、これらのツールはiOS SDKをラップしているため、一部の機能に制限がある場合があります。\n\n
また、C++やObjective-C++などの言語を使用してiOSアプリを作ることもできます。
ただし、iOS SDKの一部の機能にアクセスするためには、Objective-CやSwiftの知識が必要になる場合があります。\n\n
総じて、iOSアプリを作るためには、iOS SDKを使用する必要がありますが、他の言語を使用することも可能です。
ただし、iOS SDKの機能にアクセスするためには、Objective-CやSwiftの知識が必要になる場合があるため、
これらの言語を学ぶことをお勧めします。","role":"assistant"}}],"created":***,"id":"***","model":"gpt-3.5-turbo-0301","object":"chat.completion","usage":{"completion_tokens":348,"prompt_tokens":27,"total_tokens":375}}

[C++] 270 SDL : テトリス画面収録 その2 psコマンド プロセスID

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

CPUの別コアで並列実行した画面収録をゲームオーバー時に自動で終わらせるようにしました。前回発生したx座標のズレは修正済です。

psコマンドでFFmpeg関連のプロセスIDを取得し、順次killコマンドで停止するという内容です。

ただappファイルについては収録開始時に環境設定のセキュリティで画面収録を都度許可する必要がありプレイに支障があります。おそらくApple公証を通せば許可不要になると思います。実行ファイルは問題ありません。

ChatGPTを使うようになってから、プログラミングに関する調査でGoogleを使う頻度が激減しました。ただ一見考えているようで何も考えていないので、自身のコード検証力が問われるところです。

// 文字列分割(psコマンドにおける出力を処理)
vector<string> splitString(string s, string delimiter) {
    vector<string> tokens;
    size_t delimiterPos = s.find(delimiter);
    while (delimiterPos != string::npos) {
        string token = s.substr(0, delimiterPos);
        tokens.push_back(token);
        s.erase(0, delimiterPos + delimiter.length());
        delimiterPos = s.find(delimiter);
    }
    tokens.push_back(s);
    return tokens;
}

// プロセスID取得
vector<string> getProcessID(){
	FILE* pipe = popen("ps -ef | grep ffmpeg", "r");
    if (!pipe) return {};
	
    char buffer[512];
    string output = "";
    while (!feof(pipe)) {
        if (fgets(buffer, 128, pipe) != NULL)
            output += buffer;
    }
    pclose(pipe);

	cout << "output: " << output << endl;
	string userID = output.substr(2, 3);
	cout << "userID: " << userID << endl;

	vector<string> tokens = splitString(output, userID);
	vector<string> processIDs;
	for (int i = 0; i < tokens.size(); i++){
		cout << "token: " << tokens[i] << endl;
		string id = tokens[i].substr(1, 5);
		processIDs.push_back(id);
		cout << "id: " << id << endl;
	}

    return processIDs;
}

// プロセスID停止
void stopProcess(){
	vector<string> ids = getProcessID();

	for (string id: ids){
		string cmd = "kill " + id;

		if(!system(cmd.c_str())){
			cout << "id不適合" << endl;
		};
	}
}

[C++] 269 SDL : テトリス画面収録 その1 FFmpegを並列実行

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

テトリスのプレイ画面収録機能を実装中です。

ffmpegコマンドをCPUの別コアで並列実行します。コマンドの末尾に&を付けると別コアでの実行になります。そうしないと録画が終わるまでゲーム開始が保留状態です。

録画開始はスムーズですが、録画停止についてはプロセスIDを終了させる必要があります。これからコードを書いていきます。

画面のx座標がなぜかズレているため要調整です。

ズレて収録されたプレイ動画
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 = {};
						modeDetect();

						// 画面収録開始						
						string cmd = "/opt/homebrew/bin/ffmpeg -f avfoundation -framerate 30 -video_size 1280x720 -i \"Capture screen 0\" -vf \"crop=1280:720:400:150\" -vcodec libx264 -pix_fmt yuv420p -r 30 -preset ultrafast -tune zerolatency -crf 28 -x264-params \"nal-hrd=cbr:force-cfr=1\" -maxrate 3M -bufsize 6M /Users/[ユーザID]/TetrisDX/video/test.mp4 &";
						system(cmd.c_str());

						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;
					}
					case 9:{	// L1ボタン:スタート画面
						m_gameState = kGameState_TitleScreen;
						break;
					}
					case 6:{	// optionsボタン:ゲーム終了
						// cout << "ゲーム終了" << endl;
						bDone = true;
						break;
					}
					default:{
						// cout << "default" << endl;
						break;
					}
				}
			}
		}

		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();
	}
}