※この記事は「【前編】生成AI(ChatGPT)と一緒にC言語で簡単なアプリを作ってみた」の後編です。前編を読んでいない方は下の記事をお読みください。
「生成AIと一緒にプログラミングでいろいろ作ってみた」シリーズ第1弾。
今回はC言語で「アラーム機能付き時計アプリ」をChatGPTとペアプログラミングを行いながら作っていきます。
記事の中で、ChatGPTにした質問に対する答えのサンプルコードなどは、記事が長くなるため割愛していますが、記事の途中にアプリを実装したサンプルコードを掲載します。
それでは、いよいよ実装に取り掛かかっていきます。
※注意※
生成AIから提供される情報は毎回同じ内容が提供されるとは限りません。また、本記事内でも生成AIからの回答内容は極力事実確認をするよう努めておりますが、ご自身の環境で得た生成AIからの回答についても内容に誤りが含まれる可能性があるため、確認しながら進めてください。
おさらい
前編では、ChatGPTとペアプログラミングを進めるにあたり、ChatGPTにする質問の全体的な流れを以下のように紹介しました。
- ペアプログラミングをしてほしいことを伝える
- 作成したいものの要件定義を伝える(今回はアラーム機能付き時計アプリ)。
- 使い方がわからないツール等があれば、インストール方法や使い方などについて質問する。
- 具体的な開発のステップについて聞く。この時も、「初心者に伝わるように」、「専門的な用語がある場合はその都度説明を加える」ことを伝える。
そして前編では1~3の質問に対するChatGPTからの回答を元に環境構築を進めました。
後編では、いよいよ4の具体的な開発ステップについて聞いていきます。
なお、使用している環境は以下の通りです。
- OS:Windows 11
- IDE(統合開発環境) :Visual Studio Community 2022
- ライブラリ:Windows API
4. 具体的な開発ステップについて聞く
環境構築が完了したら、具体的な開発ステップについて聞いていきます。
サンプルプロンプトは以下の通りです。
時計アプリを実装するために必要な具体的なプログラミングのステップについて、初心者にも伝わるように教えてください。専門的な用語がある場合は、その都度説明を加えてください。
ここでのポイントは
- 「初心者にも伝わるように」というフレーズを入れること
- 専門的な用語について説明をしてもらうようにすること
です。これにより、体感ではありますが、ソースコードの中で使用している定義や処理についての説明が少し詳しくなります。
ChatGPTからの回答をまとめると以下の通りでした。
(ChatGPTからの回答)
・Visual Studioでのプロジェクトの立ち上げ(c言語のソースを書くファイルの用意)
・サンプルコード(ウィンドウの作成、main関数、時計機能の実装、アラーム機能の実装)
・デバッグとテストについて
この段階で大体のソースコードを提案してくれました。
ただ、私がChat GPTに聞いたときは、一回の質問でアプリに必要なすべての処理について回答されたわけではなく、アラーム時刻を追加するための処理や、アラームに設定した時刻と現在の時刻が一致しているか等の処理までは回答されていなかったため、Chat GPTの回答の内容に合わせ足りない点については都度質問を重ねていく形になります。
例となるサンプルプロントは以下の通りです。
アラーム時刻を追加する処理、および、アラームに設定した時刻と現在の時刻が一致しているか判定する処理を実装してください。
提案されたソースコードを基にアプリを実装する
このあとは提案されたソースコードを組み合わせていく作業となります。
このときの注意点は、chatGPTから提案されたソースコードのコピペを繰り返せばいいのではなく、どこにその処理を追加する必要があるのかを考えながら組み合わせていく必要があります。
また、提案されたソースコードの中には、当然ながら間違いが含まれていることもあり、ビルドをした際に発生したエラーやワーニングについては、どこが原因なのか自分で考えて解決していく必要があります。
エラー文の中には、ChatGPTにエラー文をそのまま質問すると解決することもありますが、それも毎回ではありません。
要するに、提案されたソースコードをそのまま信じるのではなく、
- その変数の定義は何の役割を持った変数についての定義なのか
- その関数はどういう処理をしているのか
- 処理のタイミングは適切か
などを自分で考えながら行っていくことが必要です。
これはペアプログラミングに限ったことではないですが、この考えるプロセスがプログラミングの勉強に非常に役立ちます。
以上のことに注意をしていき、ようやくアプリのソースコードが作成できました。
作成できたアプリの画面とソースコードをお見せします。
成果物(アプリ画面)
<時計画面>
チェックボックスにチェックがついているときは24時間表示、
ついていないときは12時間表示に切り替わります。
<アラーム画面>
入力ボックスにアラームを設定した時刻を入力し、「+」を押すとアラーム時刻の設定が追加されます。「-」を押すと最後についかしたアラーム時刻の設定が削除されます。(ほんとは削除したい設定時刻を選択できるようにするべきなのですが、、)
<アラーム通知ポップアップ>
アラーム時刻になるとポップアップウィンドウで通知されます。
成果物(ソースコード)
<timerapp.c>
#include <windows.h>
#include <commctrl.h>
#include <time.h>
#include <stdbool.h>
#include <stdio.h>
#include "resource.h"
int alarmCount = 0; /* 現在のアラーム数 */
HWND hHourEdit, hMinuteEdit; /* 時と分の入力ボックス用ハンドル */
Alarm alarms[MAX_ALARMS] = { 0 }; /* アラームの配列を初期化 */
HWND hAlarmLabels[MAX_ALARMS]; /* アラーム表示用のラベル */
/* メイン関数 */
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow) {
WNDCLASS wc = { 0 };
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.lpszClassName = L"ClockAlarmApp";
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
RegisterClass(&wc);
HWND hwnd = CreateWindow(L"ClockAlarmApp", L"Clock with Alarm", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 400, 300, NULL, NULL, hInstance, NULL);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
/* 画面処理関数 */
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
static HWND hTab, hClockLabel, hAddAlarmBtn, hRemoveAlarmBtn, h24HourCheckBox, hHourLabel, hMinuteLabel;
static BOOL is24HourFormat = TRUE;
static int currentTab = 0;
switch (msg) {
case WM_CREATE: {
INITCOMMONCONTROLSEX icex;
icex.dwSize = sizeof(icex);
icex.dwICC = ICC_TAB_CLASSES;
InitCommonControlsEx(&icex);
hTab = CreateWindow(WC_TABCONTROL, NULL, WS_CHILD | WS_VISIBLE, 10, 10, 360, 200, hwnd, NULL, NULL, NULL);
TCITEM tie;
tie.mask = TCIF_TEXT;
tie.pszText = L"Clock";
SendMessage(hTab, TCM_INSERTITEM, 0, (LPARAM)&tie);
tie.pszText = L"Alarm";
SendMessage(hTab, TCM_INSERTITEM, 1, (LPARAM)&tie);
/* 時刻表示用ラベル */
hClockLabel = CreateWindow(L"STATIC", L"", WS_CHILD | WS_VISIBLE | SS_CENTER,
50, 50, 250, 30, hwnd, NULL, NULL, NULL);
/* 12-24時間表示切替用チェックボックス */
h24HourCheckBox = CreateWindow(L"BUTTON", L"24-Hour Format", WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,
110, 100, 150, 30, hwnd, (HMENU)IDC_CHANGE_TIMETYPE, NULL, NULL);
SendMessage(h24HourCheckBox, BM_SETCHECK, BST_CHECKED, 0);
/* "Hour"入力ボックス */
hHourLabel = CreateWindowW(L"static", L"Hour:", WS_CHILD,
40, 150, 50, 25, hwnd, NULL, NULL, NULL);
hHourEdit = CreateWindowW(L"edit", L"", WS_CHILD | WS_BORDER | ES_NUMBER,
100, 150, 50, 25, hwnd, (HMENU)IDC_HOUR_EDIT, NULL, NULL);
/* "Minute"入力ボックス */
hMinuteLabel = CreateWindowW(L"static", L"Minute:", WS_CHILD,
180, 150, 50, 25, hwnd, NULL, NULL, NULL);
hMinuteEdit = CreateWindowW(L"edit", L"", WS_CHILD | WS_BORDER | ES_NUMBER,
240, 150, 50, 25, hwnd, (HMENU)IDC_MINUTE_EDIT, NULL, NULL);
/* "+"ボタン */
hAddAlarmBtn = CreateWindowW(L"button", L"+", WS_CHILD | BS_PUSHBUTTON,
80, 180, 50, 25, hwnd, (HMENU)IDC_ADD_ALARM, NULL, NULL);
/* "-"ボタン */
hRemoveAlarmBtn = CreateWindow(L"BUTTON", L"-", WS_CHILD | BS_PUSHBUTTON,
220, 180, 50, 25, hwnd, (HMENU)IDC_DELETE_ALARM, NULL, NULL);
/* アラーム表示用ラベル */
for (int i = 0; i < MAX_ALARMS; i++) {
hAlarmLabels[i] = CreateWindowW(L"static", L"", WS_CHILD,
60, 40 + (i * 20), 200, 20, hwnd, NULL, NULL, NULL);
}
/* 定期処理を設定 */
SetTimer(hwnd, IDT_TIMER1, 1000, NULL);
break;
}
case WM_TIMER: {
/* 定期処理 */
if (wParam == IDT_TIMER1) {
/* 時刻を更新 */
UpdateClock(hClockLabel, is24HourFormat);
/* アラームをチェック */
CheckAlarms(hwnd);
}
break;
}
case WM_COMMAND: {
switch (LOWORD(wParam)) {
/* "+"ボタンが押された場合 */
case IDC_ADD_ALARM:
if (alarmCount < MAX_ALARMS) {
/* アラーム時刻を追加 */
AddAlarmFromInput(hwnd);
break;
}
else {
MessageBox(hwnd, L"アラームの最大数に達しました。", L"アラーム追加", MB_OK);
break;
}
/* "-"ボタンが押された場合 */
case IDC_DELETE_ALARM:
/* 最新のアラームを削除 */
RemoveAlarm(hwnd);
break;
/* 時刻表示フォーマット変更処理 */
case IDC_CHANGE_TIMETYPE:
is24HourFormat = (SendMessage(h24HourCheckBox, BM_GETCHECK, 0, 0) == BST_CHECKED);
UpdateClock(hClockLabel, is24HourFormat);
break;
}
break;
}
/* タブ切り替え時の処理 */
case WM_NOTIFY: {
NMHDR* pNMHDR = (NMHDR*)lParam;
if (pNMHDR->code == TCN_SELCHANGE) {
currentTab = TabCtrl_GetCurSel(hTab);
ShowWindow(hClockLabel, currentTab == 0 ? SW_SHOW : SW_HIDE);
ShowWindow(h24HourCheckBox, currentTab == 0 ? SW_SHOW : SW_HIDE);
ShowWindow(hAddAlarmBtn, currentTab == 1 ? SW_SHOW : SW_HIDE);
ShowWindow(hRemoveAlarmBtn, currentTab == 1 ? SW_SHOW : SW_HIDE);
ShowWindow(hHourLabel, currentTab == 1 ? SW_SHOW : SW_HIDE);
ShowWindow(hHourEdit, currentTab == 1 ? SW_SHOW : SW_HIDE);
ShowWindow(hMinuteLabel, currentTab == 1 ? SW_SHOW : SW_HIDE);
ShowWindow(hMinuteEdit, currentTab == 1 ? SW_SHOW : SW_HIDE);
for (int i = 0; i < MAX_ALARMS; i++) {
ShowWindow(hAlarmLabels[i], currentTab == 1 ? SW_SHOW : SW_HIDE);
}
}
break;
}
/* アプリケーション終了処理 */
case WM_DESTROY:
KillTimer(hwnd, IDT_TIMER1);
PostQuitMessage(0);
break;
/* デフォルト処理 */
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
/* 表示時刻・時刻表示フォーマット切り替え関数 */
void UpdateClock(HWND hClockLabel, BOOL is24HourFormat) {
time_t rawtime;
struct tm timeinfo;
wchar_t buffer[80];
time(&rawtime);
localtime_s(&timeinfo, &rawtime);
if (is24HourFormat) {
wcsftime(buffer, sizeof(buffer), L"%Y/%m/%d %H:%M:%S", &timeinfo);
}
else {
wcsftime(buffer, sizeof(buffer), L"%Y/%m/%d %I:%M:%S %p", &timeinfo);
}
SetWindowText(hClockLabel, buffer);
}
/* アラーム時刻追加処理 */
void AddAlarmFromInput(HWND hwnd) {
wchar_t hourText[3], minuteText[3];
int hour, minute;
GetWindowText(hHourEdit, hourText, 3);
GetWindowText(hMinuteEdit, minuteText, 3);
hour = _wtoi(hourText);
minute = _wtoi(minuteText);
if (hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59) {
if (alarmCount < MAX_ALARMS) {
alarms[alarmCount].hour = hour;
alarms[alarmCount].minute = minute;
alarms[alarmCount].active = true;
alarmCount++;
wchar_t msg[100];
swprintf_s(msg, 100, L"アラームが %02d:%02d に追加されました。", hour, minute);
MessageBox(hwnd, msg, L"アラーム追加", MB_OK);
UpdateAlarmDisplay();
}
else {
MessageBox(hwnd, L"アラームの最大数に達しました。", L"アラーム追加", MB_OK);
}
}
else {
MessageBox(hwnd, L"無効な時刻です。0~23時、0~59分で入力してください。", L"入力エラー", MB_OK | MB_ICONERROR);
}
}
/* アラーム削除処理 */
void RemoveAlarm(HWND hwnd) {
if (alarmCount > 0) {
alarmCount--;
alarms[alarmCount].active = false;
UpdateAlarmDisplay(); // 表示を更新
MessageBox(hwnd, L"最新のアラームが削除されました。", L"アラーム削除", MB_OK);
}
else {
MessageBox(hwnd, L"削除できるアラームがありません。", L"アラーム削除", MB_OK);
}
}
/* アラーム判定処理 */
void CheckAlarms(HWND hwnd) {
SYSTEMTIME st;
GetLocalTime(&st);
for (int i = 0; i < alarmCount; i++) {
if (alarms[i].active && alarms[i].hour == st.wHour && alarms[i].minute == st.wMinute) {
wchar_t msg[100];
swprintf_s(msg, 100, L"アラーム時間 %02d:%02d です!", alarms[i].hour, alarms[i].minute);
alarms[i].active = false; /* アラームを1回鳴らしたら無効化 */
MessageBox(hwnd, msg, L"アラーム", MB_OK);
}
}
}
/* アラーム時刻追加・削除時の画面更新用関数 */
void UpdateAlarmDisplay() {
wchar_t buffer[20];
for (int i = 0; i < MAX_ALARMS; i++) {
if (i < alarmCount) {
swprintf(buffer, sizeof(buffer) / sizeof(wchar_t), L"アラーム%d : %02d:%02d", i+1, alarms[i].hour, alarms[i].minute);
SetWindowText(hAlarmLabels[i], buffer);
}
else {
SetWindowText(hAlarmLabels[i], L""); /* アラームが存在しない場合は空文字 */
}
}
}
<timerapp.h>
/* 各種定義 */
#ifndef __RESOURCE__
#define __RESOURCE__
/* コマンドID */
#define IDC_ADD_ALARM 101
#define IDC_DELETE_ALARM 102
#define IDC_HOUR_EDIT 103
#define IDC_MINUTE_EDIT 104
#define IDC_CHANGE_TIMETYPE 105
/* タイマー処理ID */
#define IDT_TIMER1 1
/* アラーム最大件数 */
#define MAX_ALARMS 5
/* アラーム定義用構造体 */
typedef struct {
int hour;
int minute;
bool active;
} Alarm;
/* プロトタイプ宣言 */
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void UpdateClock(HWND hClockLabel, BOOL is24HourFormat);
void AddAlarmFromInput(HWND hwnd);
void RemoveAlarm(HWND hwnd);
void CheckAlarms(HWND hwnd);
void UpdateAlarmDisplay();
#endif
Vistual Studioにアラーム機能付き時計アプリ用のプロジェクトを作成し、timerapp.cとtimerapp.hファイルを作成したあと、上記のコードをそれぞれのファイルにコピペし、ビルドをしてみてください。
ビルドが成功した後、Ctrl + F5キーを押すことでアプリを実行することができると思います。
※「LNK2019 未解決の外部シンボル main が関数 “int __cdecl invoke_main(void)” (?invoke_main@@YAHXZ) で参照されました」というエラーが出た場合は、Visual Studioに作成しているアプリケーションがWindowsアプリケーションであるということを教える必要があります。(エラーの内容はコンパイラがmain関数を見つけられていないという内容です。)
Visual Studioのメニューバーから「プロジェクト」→「プロパティ」→「構成プロパティ」→「リンカー」→「システム」の「サブシステム」で「Windows(/SUBSYSTEM)」を選択してください。
※Windows APIのタブコントロール<commctrl.h>の使用にはComctl32.libライブラリのリンクが必要です。Visual Studioのメニューバーの「プロパティ」から、「リンカー」→「入力」→「追加の既存ファイル」にComctl32.libを追加してください。
時間があるときにこのコードの解説記事等も作成していきたいと考えております。
感想とまとめ
今回は、生成AIであるChatGPTと一緒に、「アラーム機能付き時計アプリ」を作成していきました。
やっていて思ったのですが、もう少しステップバイステップに実装を進めていくのかと思っていたら、ChatGPTがバンバンソースコードを出してくれるからあまり自分はコードを書きませんでした。その分、ライブラリの理解に時間を割くことができました。
学習目的でもう少し自分でコードを書いていきたければ、プロンプトを工夫する必要がありそうです。
ただ、アプリの実装にかかった時間はそんなに長くはかかりませんでした。
正確には計測しておりませんでしたが、コーディングにかかった時間は6~7時間ほどだと思います。
使用したライブラリ(Windows API)の知識は0からのスタートだったので、生成AIがなければこの数倍は実装に時間がかかっていたと思うので、大きいメリットの一つですね。
生成AIとのペアプログラミングのメリット・デメリットをまとめると以下のようになります。
- ソースコード案を提案してくれる
- ライブラリの使い方を調べる手間が省ける
- わからないことがあったときにすぐに質問できる
- 開発にかかる時間を短縮してくれる
- 提案されたソースコードに間違いがないというわけではない
- 間違いがあった場合はエラー文から自分で原因を探すことになる
- 自分が意図している回答が得られるようにプロンプトの作成に工夫が必要
生成AIとのペアプログラミングのいい点は、実装したいもののソースコード案のほとんどを提案してくれることにあると感じました。
しかしながら、変数や配列の定義や、関数の内容が、提案された内容で良いかということや、どの処理がいつ必要かといった大事なことは、ソースコードを見ながらしっかりと自分で考えることが大切です。
自分でライブラリや構文について調べる手間が省ける文、考えることに時間を割くことができるは、生成AIとのペアプログラミングの大きなメリットの1つではないでしょうか。(もちろん、書き方、ライブラリの使い方を自分で調べることができるようになることも重要なスキルです。)
今回生成AIとペアプログラミングをしてアプリを実装してみましたが、上記にあるようなメリットがあるので、総合してやってみてよかったなと思います。
皆さんもプログラミングに生成AIをぜひ使用してみてください。
コメント