コンテンツにスキップ

Week 7 (2021/11/24)

今日やること

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

まずはじめに

  • UTAS上のアンケートの記入をお願いします。
  • week3の面白作品の発表
    • 入力例と想定出力をちゃんと載せるとよいです。
    • 良い例(実行できて検証できる)
      以下のようにコンパイルします。
      $ gcc main.c
      
      その後、以下のように実行し、happyが入っているとhogeと言います。
      $ echo "happy desu!" | ./a.out
      hoge
      
    • 良くない例(説明が不十分で実行できなかったり検証できなかったりする)
      実行するとポジティブ単語に反応します
      

バージョン管理(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で引数を受け付けてプログラムの挙動を制御することは極めて一般的です。

コラム

argvのように、プログラム起動時に引数を与えてその挙動をコントロールすることは極めて一般的です。 例えばマニュアルを意味するmanコマンドを使い $ man ls とすると、lsコマンドに関する引数オプションの説明が表示されます。 この画面は上下キーで移動でき、Qを押すと終了します。

自分の書くプログラムに対しても、そのように引数を読み込めるようにしておくと有益です。これについて、例えばpythonではデフォルトで 引数処理を簡単に設定できる機能があります。

ファイルの読み書き: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で保存する

やってみよう(20分)

上記を写経しましょう

クイズ

  • バージョン管理のクイズで作ったリポジトリに、以下の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/7, 23:59)
  • 宿題リンクをslackで配付します。このリンクは公開しないでください。

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

week7_1

テトリスライクな落ちものゲームを実装しましょう。

  1. オブジェクトを横方向に動かす。
  2. オブジェクトを落とす。
  3. 落ちたオブジェクトは積み重なっていく。
  4. オブジェクトが横一列に積み重なるとそれらが消える。

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

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


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

typedef struct board {
    char field[BOARD_SIZE][BOARD_SIZE + 1];
} Board;


void print_board(Board b, Point p) {
    b.field[p.y][p.x] = 'o';

    for(int i = 0; i < BOARD_SIZE; ++i) {        
        printf("%s\r\n", b.field[i]);
    }    
}


int main (int argc, char *argv[]) {

    Board b = {.field = {
        "-----",
        "-----",
        "-----",
        "-----",
        "-----"        
    }};

    Point p = {.x = 0, .y = 0};  // 現在操作しているオブジェクトの位置
    int c = '_';  // Dummy 

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

    while (1) {
        system("clear");
        printf("Press '.' to close\r\n");        
        printf("You pressed '%c'\r\n", c);

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

        print_board(b, p);

        if ((c = getchar()) == '.') { // '.' を押すと抜ける
            break;
        }
    }

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

    return 0;
}

これを実行すると以下のような画面になります。

Press '.' to close
You pressed '_'
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など、使えるものはなんでも使っていいです。

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

課題A: 右移動

現在、dを押すと右にすすむということしかできません。また、右ハジに到達してさらに右に進むとおかしなことになります。 ここで以下を実装してください。

  • aを押すと左に進む
  • 右ハジに到達してさらにdを押しても移動しない
  • 左ハジに到達してさらにaを押しても移動しない

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

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

echo 'dddddaaaaa.' | ./a.out 2>/dev/null
この結果が
Press '.' to close
You pressed 'a'
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行のみで行います。

課題Aの自動採点

入力コマンド:

echo 'aadddddda.' | ./a.out 2>/dev/null
結果(一番最後の7行だけ表示):
Press '.' to close
You pressed 'a'
---o-
-----
-----
-----
-----

課題B: 落下

さて、次はスペースキーを押すと真下まで落下するようにしましょう。

  • 二歩右に移動
  • スペースキーを押して落下
  • このとき自分は落下したので表示されない

というパスを考えます。この結果、オブジェクトは真ん中の真下に落下します。これを実現してください。自動採点結果は以下です。

課題Bの自動採点

入力コマンド:

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

課題C: 落下した後に復帰

落下した直後に何かキーを押すと、復帰するようにしてください。すなわち、

  • 二歩右に移動
  • スペースキーを押して落下
  • 何かキーを押すと左上にあらたにオブジェクトが発生
  • 三歩右に移動
  • スペースキーを押して落下
  • 何かキーを押すと左上に発生

を実現してください。自動採点結果は以下になります。ここでは最後の14行をチェックします。

課題Cの自動採点

入力コマンド:

echo 'dd xddd x.' | ./a.out 2>/dev/null
結果(最後の14行):
Press '.' to close
You pressed ' '
-----
-----
-----
-----
--oo-
Press '.' to close
You pressed 'x'
o----
-----
-----
-----
--oo-

課題D: 積み重なる

オブジェクトは積み重ねられるようにしてください。すなわち、

  • 一歩右に移動
  • スペース
  • 何かキーを押す
  • 一歩右に移動
  • スペース

となったとき、二段積み重なるようにしてください。

課題Dの自動採点

入力コマンド:

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

課題E: 一列ぶん積み重なると消える

オブジェクトは一列ぶん横に並ぶと消えるようにしてください。 その際、積み重なっていた場合は上のものがふってきます。 以下の自動採点のコマンドを実行すると、最後の落下を押す前は

----o
-----
o----
oo---
oooo-

という盤面になり、ここでスペースを押すと

-----
-----
o----
oo---
ooooo

となり最下段が全て埋まります。ここでもう一度何かのキーを押すと、最下段が消えて、そのうえが振ってきます。 加えて、左上にオブジェクトも発生するので、以下になります。

o----
-----
-----
o----
oo---

自動採点では、最後の14行、すなわち上記の揃って消える前と消えた直後をチェックします。

課題Eの自動採点

入力コマンド:

echo '     dd dd ddd dddd ddddd d.' | ./a.out 2>/dev/null
結果(最後の14行):
Press '.' to close
You pressed ' '
-----
-----
o----
oo---
ooooo
Press '.' to close
You pressed 'd'
o----
-----
-----
o----
oo---

week7_2

さて、最後に自由課題です。main.cを編集し、好きに落ちものゲームを作ってください。 week7_1をベースにしてもいいですし、全く新しいものを作ってもいいです。 完成したら、README.mdも編集してください。README.mdに解説がちゃんと書いていない場合は、減点するかもしれません。

オリジナルのテトリスに近付けるような改造でもいいですし、全然違う別ものにしてもいいです。例えば:

  • 画面をリッチにする
  • 行が消えた場合加点し、得点表示を行う
  • 様々なオブジェクトの形が出現するようにする
  • 「s」を押すとオブジェクトが回転するようにする
  • 複数行を消せるようにする
  • 対戦機能を実装する

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

諸注意

別のソースファイル(xxx.cやyyy.h)やテキストファイルを作って配置してもいいです。巨大なバイナリをアップロードすることは控えてください。

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

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

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

注意として、GUIを扱うものは、Google Cloud Shell Editorで再現できない場合は採点しません。すなわち、OpenCV, OpenGL, DirectXなどで、GUIウインドウが出るものは、再現できません。

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

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

繰り返しになりますが、ウェブサイトや書籍などを参考にした場合はその出典を書いておいてください。

チーム

また、week7_2はチームでチャレンジしてもいいです。その場合、

  • メンバーを集める
  • メンバー全員がweek7_1の課題AからEまで終わっていることを確認
  • 代表者を決める。
  • メンバー全員が参加する形で、松井までslackで以下をDMする
    • メンバー全員の名前
    • メンバー全員のGitHubアカウント
    • 代表者が誰か
  • 代表者のweek7_2リポジトリを更新していく。(代表者以外はweek7_2リポを作らなくて大丈夫です)

をお願いします。すると、「代表リポジトリ」に対するアクセス権限を他のメンバーに与えます。あとは、今日ならったチームコーディングなどを用いて、一つのリポジトリを みんなで更新していってください。

チーム人数の目安は3人程度です。最大で5人とさせてください。また、READMEには各メンバーが主にどの部分を分担して作ったのか書いておいてください。

ちなみに、誰がどこをコーディングしたのかはgitのコミット履歴からもチェック出来ます。なので、ちゃんとコードを書いた本人がリポジトリを更新してください。「更新係を作ってその人のみが更新する」というようなことはやめてください(それをされると、松井から見た場合、更新係以外はサボっているように見えてしまいます)