コンテンツにスキップ

Week 7 (2021/11/18)

今日やること

  • バージョン管理(Git/GitHub)
  • 入出力のまとめ

まずはじめに

  • Googleツアーについて
  • UTAS上のアンケートの記入をお願いします。
  • week3の面白作品の発表

バージョン管理(Git/GitHub)

バージョン管理について勉強しましょう。独立したページにまとめました。

入出力のまとめ

Cには様々な入出力方法があります。これまでの講義ではprintfによる書式付き表示、putchar/getcharによる一単語の入出力、argvによる引数受付、を学びました。 Cには他にも様々な入出力関数があります。ここでは有名どころをいくつか紹介し、簡単にまとめておきましょう。

書式付き入出力:printf, scanf

printfは書式付きの表示関数です。すなわち、何を表示するかを文字列で指定して、その中に変数を埋め込むことができます(これを書式文字列といいます)。 その内容を、標準出力に表示します。 これは既にみなさんよく知っていますね。

printf("ID:%d\n", 13);          // 表示する書式文字列を渡し、その中に変数を埋め込む
リダイレクトを思い出すと、この内容を標準出力ではなくファイルに書き出すことも簡単にできます。上記をコンパイルして以下を実行してみましょう。
$ ./a.out
ID:13                  <- 標準出力に表示。すなわちターミナルに表示。
$ ./a.out > hoge.txt   <- リダイレクトでファイルに書き出す。hoge.txtというファイルが出来てその中にID:13が書かれる。
                       <- ターミナルには表示されない。
これが最も簡単な「書き出し」です。たとえば実験を行って結果を保存したいときに、一番簡単なのはprintfしてそれをリダイレクトで吐き出すことです。

さて、printfはすなわち変数書式文字列に埋め込み標準出力に表示するものでした。 scanfはこの逆の操作をします。 すなわち、標準入力からのストリームを、書式文字列で受け取り、変数に与えます。

int n;
scanf("ID:%d", &n);
printf("n is %d\n", n);  // 確認
ここでは書式文字列に対応する変数として&nすなわちintのアドレスを与えています。 上記を実行すると、端末が入力を受け付ける待機状態になります。そこでID:13のように書式文字列に沿って打ち込むと、 nには13がセットされます。

コラム

なぜここではnのアドレスを渡す必要があるのでしょうか? Cの基本ルールとして、関数が呼び出し元の変数を編集するためにはポインタを経由しないといけなかったことを思い出しましょう。 ここではアドレスをポインタ経由で渡すので、scanf関数は呼び出し元のnを変更できます。

ここで注意として、入力でミスがあると正しく値がセットされません。例えばID :13と入力する(空白を打ってしまうミス) と、その入力は書式文字列と合わないので、nが正しく読み込まれません。 また、書式のルールはprintfに似ているのですが色々違います。例えば、printfでは%fでdoubleもfloatも表示できましたが、 scanfでは%fはfloat用で、doubleには%lfを使います。 以上により、scanfは簡単で便利なのですが、非常にミスが起きやすいので注意が必要です。

標準出力、リダイレクト、パイプ

さて、scanfは標準入力からストリームを読み込みます。すなわち、キーボードからの入力を受け付けます。 標準入力から受け付けるものは、以下の二つに変更出来たことを思い出しましょう:(1) リダイレクトを使ってファイルからの読み込み、(2) パイプを使って実行時に渡す

リダイレクトでファイルから読み込む場合を考えましょう。上のprintfのときに作った、ID:13とだけ書いてあるファイルhoge.txtを準備しましょう。 そのうえで、次を実行します

$ ./a.out < hoge.txt
これにより、読み込みは標準入力(キーボード)からではなく、hoge.txtからに変更されます。こうすることで、キーボード入力ではなく、ファイルから入力を受け付けることができます。

次にパイプについて思い出しましょう。

$ echo ID:13 | ./a.out
パイプは、直前のコマンドの標準出力を、自分の標準入力にします。上の例ではechoにより「ID:13」という単語列がechoの標準出力に吐き出されます。これがパイプを通してa.outの標準入力に 入ります。結論として、echoと組み合わせることで、コマンドの実行時にキーボード入力と同じものを渡すことができます。

これはscanfに限らず、標準入力(キーボード)から入力を受けつけるものは、上記のようにリダイレクトとパイプを使うことができます。

一単語の入出力:putchar, getchar

一単語の入出力は week3ですでにやりましたね。以下のようになります。

int c = 'a';  // "a"ではないことに注意
putchar(c);
上記の表記により、標準出力(ターミナル)に一語を書き出すことができます。これは単純な一語の出力なので、改行(\n)もしません。

コラム

'a'のような文字にはcharを使ってきましたが、charは単なる小さな整数なので、intで表現しても問題ありません。putcharの引数、およびgetcharの返り値の型はintです。

また、標準入力(キーボード)から一語を読み込むときは次のようにすることを既に習いました。

int ret;
ret = getchar();
このgetcharも、リダイレクトやパイプを使うことができることも既に習った通りです。 getcharは一語ずつ読み込むので動作が簡便ですが、たとえば三桁の数字を入力しようと「112 Enter」のようにしても、retには'1'だけが入る点に注意してください。

引数からの入力:argv

次に、引数からの入力について復習しましょう。コマンドを実行するとき、その後ろに空白を伴って引数を入力することができます。

#include <stdio.h>
#include <stdlib.h>

int main (int argc, char *argv[]) {
    if (argc != 4) {
        printf("Error. #arg must be 3: method, seed, and v0\n");
        return -1;
    }
    char *method = argv[1];
    int seed = atoi(argv[2]);
    double v0 = atof(argv[3]);

    printf("method: %s\nseed: %d\nv0: %f\n", method, seed, v0);

    // do something here
}
例えば上のプログラムは、引数の一つ目に手法名、二つ目に乱数の種、三つ目に計算の初期値を入れるようなプログラムです。 それぞれの値は、文字列であればそのまま使えますし、intdoubleであればatoiatofで変換できます。 このプログラムは以下のように実行できます。
$ ./a.out runge_kutta 123 0.5
method: runge_kutta
seed: 123
v0: 0.500000
このように、argvで引数を受け付けてプログラムの挙動を制御することは極めて一般的です。

ファイルの読み書き:fopen, fclose, fprintf, fscanf, etc

さて、上ではリダイレクトを使ってファイルに対する読み書きを行ってきましたが、 それは標準出力を無理やり変更したものでした。 よりキチンとファイルに出力するためには、ファイルを操作する関数を使います。fopenfscanfのように、先頭にf: fileがついているものです。 ファイルの読み書きについてはソフトウェア2で詳しくやりますので深入りしませんが、例えば次のようにします。

char filename[] = "hoge.txt";

// ここからがファイル読み込みの定型文
FILE *fp;
fp = fopen(filename, "r");  // "read"
if (fp == NULL) {
    printf("Cannot open %s\n", filename);
    return -1;
}

// ここでfpを経由してファイルからデータを読み込む
int n;
fscanf(fp, "ID:%d", &n);
printf("n is %d\n", n);  // 確認

// ここでfpの後始末
fclose(fp);

ここで、FILE *fpとは、ファイルの操作をつかさどるファイルポインタというものです。 fopenとう関数でファイルを開きます。その「開いているファイルを指し示す印」がfpです。 もしファイルのオープンに失敗していると、fpNULLになります。 ここで、fopenの二つ目の引数は、どういうモードでファイルを開くかを決定します。 読み込むだけのときはread: "r"に、書き込むときはwrite: "w"に、追記モードにするときはappend: "a"にします。

ファイルのオープンに成功した場合、fpを経由して、先頭にfがつく関数で読み書きができます。 たとえばfscanf(fp, ...)では、scanfと似たような挙動をしますが、その読み込み元が標準入力(キーボード)ではなくfp、すなわち 今開いたファイルになります。

全ての処理が終ったあとは、ファイルポインタを開放しておくとよいです。それがfcloseです。 上の例では開放しなくてもプログラムが終了しますが、例えば別の関数の中でfopenなどを行った場合は、ちゃんとfcloseしておかないと 問題が発生するかもしれません。

上はファイルを読み込む例でしたが、下は書き込む例です。

#include <stdio.h>

int main (int argc, char *argv[]) {   
    // argvを使い、引数からファイル名をうけとる
    if (argc != 2) {
        printf("Error. #arg must be 1: filename\n");
        return -1;
    }
    char *filename = argv[1];

    // ここからがファイル書き込みの定型文
    FILE *fp;
    fp = fopen(filename, "w");   // "write"
    if (fp == NULL) {
        printf("Cannot open %s\n", filename);
        return -1;
    }

    // ここでfpを経由してファイルにデータを書き込む
    fprintf(fp, "ID:%d", 13);

    // ここでfpの後始末
    fclose(fp);
}
ここでは完全なプログラムの形式にしました。そして、filenameは引数から受け付ける形式にしました。 これにより、$ ./a.out fuga.txt のようにして、fuga.txtに情報を書き込むことができます。

ファイルへの入出力には他にもいろいろ方式があります。とくに、上記はテキストの読み書きでしたが、数値を実際に保存するときはバイナリとして 保存します。これはwrite binary: wbのようなモードを使います。 そして、freadfwriteといった関数を使います。

個人的なオススメ

さて、色々紹介して疲れたと思いますので、実際にこれから講義や研究でコードを書く上でどれを使えばいいかについて、以下に松井のオススメ例を示します。

  • 短い入力を受け付けるときは、argvで受け取って文字列を解析する。文字列を読みたい場合はそのままだし、数値であればatoiatofだけで読み込めると楽(というか、argvで受け付けるべきなのはそういう短いものだけ)
  • 長い入力(宿題でやった時系列データとか)を受け付けるときは、freadfopenで開く。より正確には、argvでファイル名を受け取り、それをfreadなどで開いて読み込む。
  • とりあえず簡単に出力したいときは、リダイレクトを使う $ ./a.out > out.txt
  • ちゃんと出力するときは、fwritefopenで保存する

やってみよう(40分)

上記を写経しましょう

クイズ

  • バージョン管理のクイズで作ったリポジトリに、以下のcompute_squares.cを追加しましょう。
    • 引数から配列長Nとファイル名filenameを受け取る
    • filenameの最初の行には、Nの値を書き込む。
    • 0, 1, 2, .., N-1について、それぞれ二乗する
    • 二乗したそれぞれについて、filenameに対し、一行に一つの数字が入るようにして書き出す
    • 出力例:./compute_squares 4 out.txt
    • こうするとout.txtは以下のようになる
      4
      0
      1
      4
      9
      
  • 上で出力されたファイルを読み込み、さらに整数を一つ受け取り、その整数がout.txtに書かれたものがチェックする関数check_val.cを追加しましょう。これもレポジトリに追加しましょう。
    • 引数からファイル名filenameと整数valを受け取る
    • filenameからNと数列を読み込み、valが含まれているか調べる。
    • 例えば上の出力例の通りN=4out.txtが作られているとします。
    • 出力例;./check_val out.txt 12 <- 含まれない、というなんらかのメッセージを吐く
    • 出力例;./check_val out.txt 9 <- 含まれる、というなんらかのメッセージを吐く
答え
  • 例えば以下
    #include <stdio.h>
    #include <stdlib.h>
    
    int main (int argc, char *argv[]) {   
        // argvを使い、引数からファイル名をうけとる
        if (argc != 3) {
            printf("Error. #arg must be 2: N and filename\n");
            return -1;
        }
        int N = atoi(argv[1]);
        char *filename = argv[2];
    
        // ここからがファイル書き込みの定型文
        FILE *fp;
        fp = fopen(filename, "w");   // "write"
        if (fp == NULL) {
            printf("Cannot open %s\n", filename);
            return -1;
        }
    
        // ここでfpを経由してファイルにデータを書き込む
        fprintf(fp, "%d\n", N);
        for (int n = 0; n < N; ++n) {
            fprintf(fp, "%d\n", n * n);
        }
    
        // ここでfpの後始末
        fclose(fp);
    }
    
    #include <stdio.h>
    #include <stdlib.h>
    
    int main (int argc, char *argv[]) {   
        // argvを使い、引数からファイル名をうけとる
        if (argc != 3) {
            printf("Error. #arg must be 2: N and filename\n");
            return -1;
        }
    
        char *filename = argv[1];
        int val = atoi(argv[2]);
    
        // ここからがファイル読み込みの定型文
        FILE *fp;
        fp = fopen(filename, "r");   // "read"
        if (fp == NULL) {
            printf("Cannot open %s\n", filename);
            return -1;
        }
    
        // ここでfpを経由してファイルにデータを読み込む
        int N;
        fscanf(fp, "%d", &N);
        int vals[N];
        for (int n = 0; n < N; ++n) {
            fscanf(fp, "%d", vals + n);  // vals[n]に読み込む
        }
    
        // ここでfpの後始末
        fclose(fp);
    
        // 値のチェック
        printf("N: %d\n", N);
        for (int n = 0; n < N; ++n) {
            printf("%d, ", vals[n]);
        }
        printf("\n");
    
        for (int n = 0; n < N; ++n) {
            if (vals[n] == val) {
                printf("Found the value!: %d\n", val);
                return 0;
            }
        }
        printf("Cannot found the value: %d\n", val);
        return 0;
    }
    

最終課題

それでは最終課題です。

  • 締切は 二週間後の深夜23:59までです(今回の場合、2021/12/1, 23:59)
    • 【11/24 更新】締切は2021/12/8 23:59とします。
    • 【11/30 更新】回路と数学演習の試験があるとのことなので、締切を2021/12/12 23:59に延長します。
  • 宿題リンクをslackで配付します。このリンクは公開しないでください。

注意:Google Cloud Shell Editorの内容は、しばらく使っていないと消えてしまいます。重要なファイルは、自分の手元のローカルマシンにダウンロードしておいてください。

week7_1

ボンバーマンライクなゲームを実装しましょう。ボンバーマンとは、フィールドに爆弾を置いて相手を倒す対戦ゲームです。

ここで基本的な流れは

  1. キャラクター(ボンバーマン)はフィールド上を動く。フィールド上の障害物や、フィールドの外に進むことはできない。
  2. ボンバーマンは自分がいる位置に爆弾を置くことが出来る。爆弾はしばらくすると爆発し、十字上の炎を発生させる。この炎にボンバーマンが触れると、そのボンバーマンはゲームオーバーになる。炎もまた、障害物やフィールド外に進むことはできない。
  3. 壁に炎をあてると壁を壊すことが出来る。
  4. 壁の中からアイテムが出てくることがある。アイテムに触れると能力をゲットできる。例えば「炎アイテム」をゲットすると、十字の炎の射程が伸びる。「爆弾アイテム」をゲットすると、一度に二度爆弾を置くことが出来る。
  5. 爆弾の炎に他の爆弾が触れると、「連鎖」となり、同時に全ての爆弾が爆発する。
  6. 上記のルールのもと、壁を壊してアイテムを集めながら、他のボンバーマンを攻撃し、最後まで生き残ったものが勝者となる。

今回はまず、基本的な「1」「2」の部分を実装しましょう。

main.cには以下のような雛形コードが書いてあります。

#include <stdio.h>
#include <stdlib.h>
#define BOARD_SIZE 5


typedef struct point {
    int x;
    int y;
} Point;


void print_board(const char board[][BOARD_SIZE + 1]) {
    for(int i = 0; i < BOARD_SIZE; ++i) {
        printf("%s\r\n", board[i]);
    }
}


int main (int argc, char *argv[]) {
    printf("Press any key to conitnue\n");


    char board[BOARD_SIZE][BOARD_SIZE + 1] = {
        "-----",
        "-#-#-",
        "-----",
        "-#-#-",
        "-----"
    };

    Point p = {.x = 0, .y = 0};  // 自分の位置
    int c;

    system("/bin/stty raw onlcr");  // enterを押さなくてもキー入力を受け付けるようになる

    while((c = getchar()) != '.') {   // '.' を押すと抜ける
        system("clear");
        printf("Press '.' to close\r\n");        
        printf("You pressed '%c'\r\n", c);

        if (c == 'd') {
            ++p.x;
        } 

        board[p.y][p.x] = 'o';
        print_board(board);
        board[p.y][p.x] = '-';

    }

    system("/bin/stty cooked");  // 後始末

    return 0;
}

これを実行するとPress any key to continueと表示されます。そこで何かキー(例えばq)を押すと、以下のような画面になります。

Press '.' to close
You pressed 'q'
o----
-#-#-
-----
-#-#-
-----
この画面がボンバーマンの盤面を表しています。ここではフィールドの大きさは「5マス x 5マス」です。-が歩ける部分を表示しており、#は歩けない障害物を表しています。 oがボンバーマンです。ここではdを押すと右に一歩進むようになっています。dを押してみましょう。

Press '.' to close
You pressed 'd'
-o---
-#-#-
-----
-#-#-
-----

このように、右に一歩移動しました。これは、キー入力を受け付ける度にsystem("clear")で画面をリフレッシュすることで実現しています。 ここで、ピリオド(.)を押すとゲームが終了するようになっています。

ここで注意があります。system("/bin/stty raw onlcr"); // enterを押さなくてもキー入力を受け付けるようになる および system("/bin/stty cooked"); // 後始末 という表記があるかと思います。参考リンク。この表記により、わざわざエンターキーを押さなくても、すぐにキー入力を受け付けてくれるようになっています。注意として、

  • この表記で囲まれている間は、改行するときに\nではなく\r\nとする必要があります。
  • 終了させるときに、Ctrl+C で終了できなくなります。なので、ここではピリオドを終了キーとして用いています。

ということがありますので気を付けてください。

この雛形コードを拡張していってもいいですし、全く別のコードを書いても大丈夫です。自動採点をパスできるのであればなんでも大丈夫です。

また、string.hstdlib.hなど、使えるものはなんでも使っていいです。

さて、それでは以下のタスクを出来るところまでやってみてください。

課題1

現在、dを押すと右にすすむということしかできません。ここで以下を実装してください。

  • wを押すと上に進む
  • aを押すと左に進む
  • sを押すと下に進む

この結果、./a.outしたあとにddssaawwと入力すると最初の位置に戻ってくるようになります。

このことは、以下のようなコマンドで確かめることが出来ます。

echo 'ddssaaww.' | ./a.out 2>/dev/null
この結果が
Press '.' to close
You pressed 'w'
o----
-#-#-
-----
-#-#-
-----
となっていればOKです。

ここでは、echoをパイプで繋げることで、手打ちでキーボード入力する部分を記述しています。また、最後のピリオドを忘れるとプログラムが終了しなくなるので注意してください。終了しなくなった場合、Google Cloud Shell Editorだと、Ctrl+Cを連打しまくるとそのうち止まります。

コラム

ちなみにここで2>/dev/nullの部分は、

  • 標準エラー出力である2の宛先を、
  • >を使って、画面ではなく別の部分に飛ばす。
  • 飛ばす先は/dev/nullである

ということを意味しています。今回は詳細は省きますが、プログラムの出力には「標準出力」と「標準エラー出力」の2種類があります。どちらもデフォルトでは画面(ターミナル)に出力されるので特に違いを意識しません。エラーに関するものは「標準エラー出力」に出力されています。今回はその宛先を/dev/nullという「ゴミ箱」に変えました。 どういうことかというと、この部分をつけずにプログラムを実行すると /bin/stty: 'standard input': Inappropriate ioctl for device のようなエラーメッセージが表示されてしまうと思います。 今回は自動採点でこの部分を省きたいので、明示的に標準エラー出力を変更しました。詳細についてはここここを読んでください。

さて、自動採点を行うときは、system("clear")で画面外に飛ばした部分も含めて、以下のようになります。一行目には謎のコマンドがいろいろ表示されるのですが、この部分はsttyに不随してくるゴミなので、気にしないでください。採点は一番下の7行のみで行います。

課題1の自動採点

入力コマンド:

echo 'ddssaaww.' | ./a.out 2>/dev/null
結果(一番最後の7行だけ考慮):
Press any key to conitnue
Press '.' to close
You pressed 'd'
-o---
-#-#-
-----
-#-#-
-----
Press '.' to close
You pressed 'd'
--o--
-#-#-
-----
-#-#-
-----
Press '.' to close
You pressed 's'
-----
-#o#-
-----
-#-#-
-----
Press '.' to close
You pressed 's'
-----
-#-#-
--o--
-#-#-
-----
Press '.' to close
You pressed 'a'
-----
-#-#-
-o---
-#-#-
-----
Press '.' to close
You pressed 'a'
-----
-#-#-
o----
-#-#-
-----
Press '.' to close
You pressed 'w'
-----
o#-#-
-----
-#-#-
-----
Press '.' to close
You pressed 'w'
o----
-#-#-
-----
-#-#-
-----

課題2

さて、現在は障害物(#)の上に乗ることが出来てしまいます。また、画面の外に出ることもできてしまいます。 なので、障害物や画面外に移動しようとすると移動しないようにしてください。 例えば、

下、 右(障害物があるので移動しない)、 下、 下、 左(画面外なので移動しない)、 下、 下(画面外なので移動しない)、 右、右、右、右、右(画面外なので移動しない)、 下(画面外なので移動しない)

というパスを考えます。この結果、自分は一番右下になります。これを実現してください。自動採点結果は以下です。

課題2の自動採点

入力コマンド:

echo 'sdssassddddds.' | ./a.out 2>/dev/null
結果(最後の7行):
Press '.' to close
You pressed 's'
-----
-#-#-
-----
-#-#-
----o

課題3

さて、次は爆弾を置けるようにしましょう。Spaceを押す(すなわち、空白である「」を打ち込む)と、その場に爆弾を置けるようにしましょう。 爆弾を置いたあとに移動すると、爆弾がおかれている場所にはBが表示されるようになります。 また、爆弾が置いてある状態で、別のところに再度爆弾は置けないようにしましょう。 以下の自動採点の例では、移動して爆弾を置いたあとに、再度爆弾を置こうとしますが、置けません。

課題3の自動採点

入力コマンド:

echo 'echo 'dds s ds.' | ./a.out 2>/dev/null
結果(最後の7行):
Press '.' to close
You pressed 's'
-----
-#B#-
---o-
-#-#-
-----

課題4

さて、爆弾を置いたあとに、6回キー入力を受け付けると、爆弾が爆発します。6回目のキー入力時に、爆弾を中心に上下左右2マス分だけの火炎を発生させてください。 この火炎はFで表現してください。

課題4の自動採点

入力コマンド:

echo 'ddddss ssaaaa.' | ./a.out 2>/dev/null
結果(最後の7行):
Press '.' to close
You pressed 'a'
----F
-#-#F
--FFF
-#-#F
o---F

課題5

それではいくつか細かい点を確認しましょう。まず、爆弾を置いたあとは、その爆弾の上に移動しようとしても移動できません。 これは、s swとしたときの結果が

-----
B#-#-
o----
-#-#-
-----
のようになるということです。

また、火炎が障害物で遮蔽されている場合、火炎はその向こうにはいきません。 これは、s swddsaとしたときの結果が

F----
F#-#-
F----
F#o#-
-----
となることで確認できます。

最後に、爆発が終ったあとは、爆弾も火炎も消えて元通りになります。これらをまとめた自動採点が以下です。

課題5の自動採点

入力コマンド:

echo 's swddsas.' | ./a.out 2>/dev/null
結果(最後の14行):
Press '.' to close
You pressed 'a'
F----
F#-#-
F----
F#o#-
-----
Press '.' to close
You pressed 's'
-----
-#-#-
-----
-#-#-
--o--

課題6

さて、爆弾を一度置いて爆発させた後も、再度別のところに爆弾を置いてまた爆発させられることを確認しましょう。

課題6の自動採点

入力コマンド:

echo ' ddssda ddssad.' | ./a.out 2>/dev/null
結果(最後の7行):
Press '.' to close
You pressed 'd'
--F--
-#F#-
FFFFF
-#F#-
--F-o

課題7

さて、炎が直撃するとGame over!と表示して終了するようにしましょう。ここまでで一区切りです。

課題7の自動採点

入力コマンド:

echo 'dd ssaaww.' | ./a.out 2>/dev/null
結果(最後の8行):
Press '.' to close
You pressed 'w'
oFFFF
-#F#-
--F--
-#-#-
-----
Game over!

課題8

さて、最後に自由課題です。main2.cを作り、自由に編集し、自分オリジナルのボンバーマンライクなゲームを作ってください。 課題1~7までをベースにしてもいいですし、全く新しいものを作ってもいいです。 完成したら、README.mdも編集してください(README.mdに課題8の解説がちゃんと書いていない場合は、減点するかもしれません)

オリジナルのボンバーマンにある色々な要素を足してもいいですし、オリジナルなルールを作ってもいいです。例えば:

  • 画面をリッチにする
  • 爆弾を複数置けるようにする
  • 爆弾の連鎖を実現する
  • 壁を設置する。壁を壊せるようにする。
  • 壁を壊すとアイテムが出てくるようにする。
  • アイテムを実装する
  • 爆弾の爆発をターン制ではなく時間制にする
  • 二人対戦を実現する
  • AIとの対戦を実現する
  • 二人遠隔対戦を実現する

優秀作品は、ソフ1メンバーに共有します。

また、課題8はチームでチャレンジしてもいいです。その場合、複数人で一つのリポジトリを更新してゲームを完成させてください。チームを組みたい人は、

  • 課題1から7まで全てが終っていることを確認
  • メンバーを集める
  • 「主たる宿題リポジトリ」を誰にするか決める。そのリポジトリが採点対象となる
  • チームを組みたい人たち全員が参加する形で、松井までslackでDMする

をお願いします。すると、「主たる宿題リポジトリ」に対するアクセス権限を他のメンバーに与えます。

あとは、「主たるリポジトリ」のmain2.cをみんなで更新してください。とりあえず逐一コピペしてもいいですし、Git/GitHubの「使い方のまとめ」でならった通り、

  • 最初にcloneする(1回だけ)
  • pull, 編集, add, commit, push を繰り返す

という方向でやっても大丈夫です。ちなみにその場合はmainブランチを使いまわすことになるので、衝突が発生した場合(複数人が同じ場所を更新してしまった場合)に修正が必要なのですが、 そこは気合で乗り気ってください。本当はここでは「ブランチを切る」という作業をすると、スッキリと処理できます。ブランチについてはソフ1の範囲を越えるので、詳しくは言及しませんが、もしメンバーの中に詳しい人がいたら聞いてみてください。 (ちなみに、授業でならったforkは宿題リポジトリでは使えません。プライベートリポジトリなので。。。)

また、「主たる宿題リポジトリ」以外の宿題リポジトリは更新しないでも大丈夫です。つまり、AさんBさんCさんでチームを組み、Aさんを主たる宿題リポとしたならば、BさんCさんの宿題リポについてはmain2.cを更新しなくても大丈夫です(もちろん課題1~7に相当するmain.cのほうはちゃんとやっておいてください)

「チームを組みたいけど相手がいない。。。」と言う人は、スラックのソフ1チャンネルでメンバーを募ってみるといいです。また、電気系の人は是非学習チームの人に声をかけてみてください。電気系でない人は、松井まで一言いってもらえるとアレンジします。

【2021/11/24追記】チーム人数の目安は3人程度です。最大で5人とします。また、READMEには各メンバーが主にどの部分を分担して作ったのか書いておいてください。ちなみに、誰がどこをコーディングしたのかはgitのコミット履歴からもチェック出来ます。なので、ちゃんとコードを書いた本人がリポジトリを更新してください。「更新係を作ってその人のみが更新する」というようなことはやめてください。

【2021/11/25に以下を追記】 ライブラリを追加で入れることはOKですが、Google Cloud Shell Editorで実行できることを前提にしてください。例えばライブラリをaptで入れる場合は、以下のようなものをREADMEに書いておいてください。

まずXXXのライブラリを以下のようにインストールする
$ sudo apt install XXX

コンパイルするときは以下のようにXXXのフラグをつける
$ gcc main2.c -o main -lXXX

注意として、GUIを扱うものは、Google Cloud Shell Editorで再現できない場合は採点しません。すなわち、OpenCV, OpenGL, DirectXなどで、GUIウインドウが出るものは、再現できません。 どうしてもGUI込みのものを作りたい場合は、main3.cなどを作ってGUI込みバージョンも作るのはOKですが、採点はmain2.cで行います。

ちなみに、松井の手元で採点するときは、まずWSL2+Win10で確認し、それで挙動が変ならGoogle Cloud Shell Editorで確認し、それで動かなければもうチェックしません(WSLgは入れていません)

OpenGLなどを使えばいくらでもカッコいいものは出来ると思うのですが、今回はターミナルを使うc言語入門の講義なので、ターミナルベースで動くものでイケてるゲームを追求する方向でいきましょう。

【2021/11/26追記】課題はC言語で行ってください。C++はダメです。本講義ではローレベルなポインタを扱うことが主題の一つです。C++を使うと簡単にポインタを隠蔽できるので、C++の使用は無しとます。