今回のインターンシップの選考課題では、ターミナル上で動作する「しりとりアプリ」をC言語で実装して提出していただきます。
コンパイラの種類(gcc / clang)に制限はありませんので、お手元の環境で取り組んでください。
AIの活用について制限はありません、積極的に活用してください。
ただし、後述の通り、使用した場合は「どの部分にどのように活用したか」を README に記入してください。
ターミナル上で動作する「しりとりアプリ」をC言語かC++で実装して提出してください。
この資料ではC言語での実装についてまとめています。参考にしてください。
ソースコード一式と README を掲載した GitHub リポジトリのリンクを以下の Google フォームから提出してください。
README.mdは以下の仕様を満たすよう作成してください。 これ以外の内容が含まれていても問題ありません。
gcc shiritori.c -o shiritori && ./shiritori)課題提出用Googleフォーム
https://forms.gle/v2rkvJUsamCrWWvS7
まず、以下の仕様を満たすように実装してください。
追加で、便利・面白いと思う任意の機能を考えて、最低2つ以上実装してください。以下に例を示します。
GitHubアカウント作成後、数日経過しなければ使用できない機能もあるため、早めに登録することをおすすめします。
作成したCLIアプリの提出等に必要なため、GitHubアカウントを作成しましょう。
以下のWebサイトからアカウント登録を行ってください。
既にアカウントをお持ちの方は、そちらのアカウントを使用していただいて構いません
Microsoftが提供しているソースコードエディタです。VSCodeとも呼ばれます。
開発に必要な機能の多くを搭載しており、プラグインの開発も企業・個人問わず行われているので、特に拘りが無ければインストールをおすすめします。
既にお気に入りのエディタがある場合は、インストールせずに進めていただいても構いません。
公式サイトの説明に従い、Visual Studio Codeをインストールしてみましょう。
https://code.visualstudio.com/
ご自身のOSに合わせたものをダウンロードして、インストールしましょう。
VSCode上で、Ctrl(Macの場合はCmd)とJを押すことで、下部のパネルを表示を切り替えることができます。
パネル内にある「ターミナル」からコマンドを実行することができるため、コマンドの実行はこちらを使用してください。
WindowsのPowerShellやMacのターミナルを使用しても構いません。
C言語のソースコードをコンパイル(実行可能ファイルに変換)するために、Cコンパイラをインストールします。
macOS では Xcode Command Line Tools をインストールすることで、clang(C/C++コンパイラ)が使えるようになります。
ターミナルを開いて、以下のコマンドを実行してください。
xcode-select --install
インストールダイアログが表示されるので、案内に従ってインストールを完了させます。
完了したら、以下のコマンドでバージョン情報が表示されることを確認しましょう。
# 入力
cc --version
# 出力例
Apple clang version 17.0.0 (clang-1700.6.3.2)
Target: arm64-apple-darwin25.3.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
このドキュメントでは
gccというコマンド名で記載していますが、macOS ではgccを実行してもclangが呼び出されるようになっているため、そのままgccを使って構いません。
Windows では WSL(Windows Subsystem for Linux)をインストールし、その中で gcc を使用します。
wsl --install
gcc をインストールします。sudo apt update
sudo apt install build-essential
build-essentialは、gccを含む C/C++ の開発に必要なツール一式をまとめたパッケージです。
# 入力
gcc --version
# 出力例
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even to the extent permitted by law. FOR A PARTICULAR PURPOSE.
以降のコマンド操作は、Windows ユーザーの場合は WSL 上の Ubuntu ターミナルで行ってください。
Hello World を出力するプログラムを作って実行してみましょう。
hello.c を作成して以下の内容を書き込みます。// hello.c
#include <stdio.h>
int main(void) {
printf("Hello, World!\n");
return 0;
}
hello.c のあるフォルダに移動し、以下のコマンドでコンパイルします。gcc hello.c -o hello
-o helloは出力ファイル名の指定です。指定しないとa.outというファイル名で出力されます。
hello という実行可能ファイルが作成されます。実行してみましょう。./hello
# Hello, World!
Hello, World! と表示されればOKです!
Topic:
printfの中身を書き換えて再コンパイル・実行してみましょう。ソースを変更したら再コンパイルが必要なことが C 言語の特徴です。
ユーザーからキーボード入力を受け取り、それをそのまま表示するプログラムを作ってみます。
shiritori.c を作成して、以下の内容を書き込みます。// shiritori.c
#include <stdio.h>
#include <string.h>
#define MAX_WORD_LENGTH 256
int main(void) {
char input[MAX_WORD_LENGTH];
printf("単語を入力してください > ");
// 標準入力から1行受け取る
if (fgets(input, sizeof(input), stdin) == NULL) {
return 0;
}
// 末尾の改行を削除
size_t len = strlen(input);
if (len > 0 && input[len - 1] == '\n') {
input[len - 1] = '\0';
}
printf("入力された単語: %s\n", input);
return 0;
}
gcc shiritori.c -o shiritori
./shiritori
単語を入力してください > りんご
入力された単語: りんご
Note: 改行の削除について
fgetsは、Enter キーで入力された改行文字(\n)も文字列の末尾に含めます。そのままだと「りんご\n」のような文字列になってしまい、後の処理(末尾と先頭の比較など)で困るので、ここで改行を削除しておきます。
Note: UTF-8 と文字列
日本語の「ひらがな1文字」は、UTF-8 では 3バイト で表されます。
例えば「り」は0xE3 0x82 0x8Aの3バイトです。strlenは文字数ではなく バイト数 を返すので、「りんご」の場合は9(3文字 × 3バイト)が返ってきます。
直前の単語を保持しておき、入力された単語との「しりとりが成立しているか」を判定します。
UTF-8 ではひらがな1文字が3バイトなので、
を strncmp で比較すれば、「末尾と先頭が同じひらがなか」を判定できます。
shiritori.c を以下の内容に書き換えます。// shiritori.c
#include <stdio.h>
#include <string.h>
#define MAX_WORD_LENGTH 256
int main(void) {
// 直前の単語を保持する
char previous_word[MAX_WORD_LENGTH] = "しりとり";
char next_word[MAX_WORD_LENGTH];
printf("しりとりを始めます!\n");
printf("(終了するには Ctrl+D を押してください)\n\n");
while (1) {
printf("前の単語: %s\n", previous_word);
printf("次の単語を入力してください > ");
// 標準入力から1行受け取る
if (fgets(next_word, sizeof(next_word), stdin) == NULL) {
printf("\nしりとりを終了します。\n");
break;
}
// 末尾の改行を削除
size_t len = strlen(next_word);
if (len > 0 && next_word[len - 1] == '\n') {
next_word[len - 1] = '\0';
len--;
}
// 空入力は無視
if (len == 0) {
continue;
}
// UTF-8 ではひらがな1文字が3バイトなので、
// 末尾の3バイトと先頭の3バイトを比較する
size_t prev_len = strlen(previous_word);
if (prev_len < 3 || len < 3) {
printf("エラー: ひらがなを入力してください\n\n");
continue;
}
if (strncmp(previous_word + prev_len - 3, next_word, 3) == 0) {
// 末尾と先頭が一致したので、直前の単語を更新
strcpy(previous_word, next_word);
printf("\n");
} else {
printf("エラー: 前の単語に続いていません\n\n");
}
}
return 0;
}
gcc shiritori.c -o shiritori
./shiritori
しりとりを始めます!
(終了するには Ctrl+D を押してください)
前の単語: しりとり
次の単語を入力してください > りんご
前の単語: りんご
次の単語を入力してください > ごりら
前の単語: ごりら
次の単語を入力してください > らっぱ
前の単語: らっぱ
次の単語を入力してください > みかん
エラー: 前の単語に続いていません
前の単語: らっぱ
次の単語を入力してください >
Topic: Step 5 までを実装したサンプルが、こちらにあります。
詰まったら参考にしてみてください。
仕様にある「『ん』で終わる単語が入力されたら終了」と「過去に使用した単語が入力されたら終了」を実装します。
UTF-8 で「ん」は 0xE3 0x82 0x93 の3バイトです。
入力された単語の末尾3バイトがこの並びと一致すれば「ん」で終わっていると判定できます。
これまでは直前の単語1つだけを保持していましたが、過去全ての単語との重複チェックが必要になるので、二次元配列を使って履歴を保持するように変更します。
shiritori.c を以下の内容に書き換えます。// shiritori.c
#include <stdio.h>
#include <string.h>
#define MAX_WORD_LENGTH 256
#define MAX_HISTORY 100
int main(void) {
// 単語の履歴をまとめて保持する
char history[MAX_HISTORY][MAX_WORD_LENGTH];
int history_count = 0;
char next_word[MAX_WORD_LENGTH];
// 初期単語をセット
strcpy(history[0], "しりとり");
history_count = 1;
printf("しりとりを始めます!\n");
printf("(終了するには Ctrl+D を押してください)\n\n");
while (1) {
const char *previous_word = history[history_count - 1];
printf("前の単語: %s\n", previous_word);
printf("次の単語を入力してください > ");
if (fgets(next_word, sizeof(next_word), stdin) == NULL) {
printf("\nしりとりを終了します。\n");
break;
}
size_t len = strlen(next_word);
if (len > 0 && next_word[len - 1] == '\n') {
next_word[len - 1] = '\0';
len--;
}
if (len == 0) {
continue;
}
size_t prev_len = strlen(previous_word);
if (prev_len < 3 || len < 3) {
printf("エラー: ひらがなを入力してください\n\n");
continue;
}
// 末尾と先頭の比較
if (strncmp(previous_word + prev_len - 3, next_word, 3) != 0) {
printf("エラー: 前の単語に続いていません\n\n");
continue;
}
// 「ん」で終わるかチェック
// UTF-8 で「ん」は 0xE3 0x82 0x93
if ((unsigned char)next_word[len - 3] == 0xE3 &&
(unsigned char)next_word[len - 2] == 0x82 &&
(unsigned char)next_word[len - 1] == 0x93) {
printf("\n「%s」は「ん」で終わるため、ゲーム終了です!\n", next_word);
break;
}
// 重複チェック
int duplicated = 0;
for (int i = 0; i < history_count; i++) {
if (strcmp(history[i], next_word) == 0) {
duplicated = 1;
break;
}
}
if (duplicated) {
printf("\n「%s」は既に使われた単語です。ゲーム終了です!\n", next_word);
break;
}
// 履歴に追加
if (history_count >= MAX_HISTORY) {
printf("\n履歴が一杯になりました。ゲーム終了です!\n");
break;
}
strcpy(history[history_count], next_word);
history_count++;
printf("\n");
}
return 0;
}
前の単語: ごりら
次の単語を入力してください > らん
「らん」は「ん」で終わるため、ゲーム終了です!
ゲーム中や終了後に、最初からやり直せるようにします。
ここでは、ユーザーが reset と入力した場合に履歴をクリアして最初の状態に戻すようにしてみましょう。
shiritori.c のメインループ冒頭付近に、リセット処理を追加します。 if (len == 0) {
continue;
}
+ // リセットコマンド
+ if (strcmp(next_word, "reset") == 0) {
+ strcpy(history[0], "しりとり");
+ history_count = 1;
+ printf("\nリセットしました。最初からやり直します。\n\n");
+ continue;
+ }
+
size_t prev_len = strlen(previous_word);
Note: ゲーム終了後のリセットについて
上記の実装では、ゲームが終了する(breakする)と while ループを抜けてしまうため、終了後のリセットができません。終了後もリセットできるようにしたい場合は、breakの代わりにフラグ変数で状態管理する、あるいは終了後に「もう一度遊びますか?」の選択肢を出すなど、ご自身で工夫してみてください。
reset と入力したときに最初の状態に戻ることを確認しましょう!前の単語: りんご
次の単語を入力してください > reset
リセットしました。最初からやり直します。
前の単語: しりとり
次の単語を入力してください >
作成したCLIアプリを GitHub リポジトリに保存し、コードを公開しましょう。
GitHub は、Git のリポジトリをインターネット上で管理するためのサービスです。リポジトリをアップロードしておくことで、複数人での開発時のソースコードの共有、ソースコードのバックアップ、ご自身の実績の公開の場として、などの効果を期待できます。
Topic: コミット、プッシュなど、Gitの操作について軽く調べてみましょう。
ここまでで、必須仕様を満たすしりとりCLIアプリを実装し、GitHub リポジトリに公開する手順を学びました。
提出は資料冒頭に記載の Google フォームからお願いします。
必ず、仕様を満たしていることを確認し、README を記載しましょう!
固定の「しりとり」ではなく、いくつかの候補からランダムに選びましょう。rand() と srand((unsigned int)time(NULL)) を使うと乱数を発生させられます。<stdlib.h> と <time.h> のインクルードが必要です。
#include <stdlib.h>
#include <time.h>
const char *initial_words[] = { "りんご", "みかん", "ぶどう", "ばなな" };
srand((unsigned int)time(NULL));
int idx = rand() % 4;
strcpy(history[0], initial_words[idx]);
入力された単語のバイト列をチェックし、UTF-8 のひらがなの範囲(U+3040 ~ U+309F)に収まっているかを確認します。
UTF-8 では、ひらがなは 0xE3 0x81 0x80 ~ 0xE3 0x82 0x9F のバイト列で表されます。3バイトずつ区切って、それぞれが範囲内かを確認しましょう。
int is_hiragana_only(const char *word, size_t len) {
if (len % 3 != 0) return 0;
for (size_t i = 0; i < len; i += 3) {
unsigned char b0 = (unsigned char)word[i];
unsigned char b1 = (unsigned char)word[i + 1];
if (b0 != 0xE3) return 0;
if (b1 < 0x81 || b1 > 0x82) return 0;
}
return 1;
}
終了時だけでなく、入力ごとに「これまでの単語履歴」を表示するようにしてみましょう。for ループで history 配列を順に printf するだけで実装できます。
「ん」を避けるために一文字のひらがな(「あ」「い」など)だけを入力されると、しりとりとしては微妙です。
入力された単語の長さ(UTF-8 で3バイト以上か)でチェックを入れることで、一文字入力を防げます。
余裕があれば「拗音(しゃ・しゅ・しょ など)」を含む単語の扱いも考えてみましょう。
fopen / fgets でテキストファイルを読み込み、辞書に載っていない単語は弾く、という実装もできます。「実在しない単語は入力できないようにする」機能の実現につながります。
<time.h> の time() を使い、入力に制限時間を設けてみましょう。select() や poll() を使うと「タイムアウト付きの標準入力読み取り」も可能です(少し難しい!)。
ぜひ自分なりの工夫を加えて、楽しい「しりとりCLI」に仕上げてみてください!