c言語でリバーシを作ってみた！！

# 動機
以前愚直に書いたものを再帰を使って書き直しました！360行が200行になりました！

https://qiita.com/haihuiliyuyuan/items/45191b227aa91547302b

# コード
<details><summary>折りたたんでます。</summary>

```c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define N 8
void init(void);
void show(void);
void put(char);
int check(void);
int over(void);
int isOk(char, int, int, int, int);
int canPut(char);
void set(char, int, int, int, int, int, int);
void reversi(char, int, int, int, int);
char table[N][N];
char P1 = 'O', P2 = 'X', NO = '-';
int turn = 0;
int main(void)
{
    srand((unsigned int)time(NULL));
    init();
    show();
    while (over())
    {
        if (turn % 2 == 0)
            put(P1);
        else
            put(P2);
        show();
        turn++;
    }
    if (1 == check())
        printf("I am a winner!\n");
    else if (0 == check())
        printf("I am a loser...\n");
    else
        printf("I am not neither.");
    return 0;
}
int over(void)
{
    int cnt = 0;
    for (int i = 0; i < N; i++)
        for (int j = 0; j < N; j++)
            if (table[i][j] == NO)
                cnt++;
    if (cnt == 0)
        return 0;
    else
        return 1;
}
void init(void)
{
    for (int i = 0; i < N; i++)
        for (int j = 0; j < N; j++)
            table[i][j] = NO;
    table[N / 2][N / 2] = table[N / 2 - 1][N / 2 - 1] = P1;
    table[N / 2][N / 2 - 1] = table[N / 2 - 1][N / 2] = P2;
}
void show(void)
{
    system("cls");
    printf("turn : %d\nO:P1 X:P2 \nplease input a coordinate. \nex : y x\n", turn);
    for (int i = 0; i < N; i++)
    {
        if (i == 0)
        {
            printf("  ");
            for (int j = 0; j < N; j++)
                printf("%d ", j);
            printf("\n");
        }
        for (int j = 0; j < N; j++)
        {
            if (j == 0)
                printf("%d ", i);
            printf("%c ", table[i][j]);
        }
        printf("\n");
    }
    printf("\n\n");
}
int check(void)
{
    int cnt = 0;
    for (int i = 0; i < N; i++)
        for (int j = 0; j < N; j++)
            if (table[i][j] == P1)
                cnt++;
    if (cnt == N * N / 2)
        return 2;
    else if (cnt > N * N / 2)
        return 1;
    else
        return 0;
}
void put(char player)
{
    int x, y;
    if (!canPut(player))
    {
        show();
        if (player == P1)
            printf("<---------The places can be put didn't exitst! Turnover!----------->\n");
        return;
    }
    while (1)
    {
        if (player == P1)
            // scanf("%d %d", &y, &x);
            x = rand() % N, y = rand() % N;
        else
            y = rand() % N, x = rand() % N;
        if (isOk(player, y, x, 0, -1)) // 置き位置として正しいか(範囲内かつ一枚でも相手コマを返せるか)
            break;
        else if (player == P1)
        {
            show();
            printf("<---------The place is not correct! Put again!----------->\n");
        }
    }
    set(player, y, x, 0, 0, 0, -1);
}
int isOk(char player, int y, int x, int flag, int dir) // 行 列　の順で座標置く
{
    char target = player == P1 ? P2 : P1;
    int i, check = 0, dirLib[8][2] = {{-1, 0}, {-1, 1}, {0, 1}, {1, 1}, {1, 0}, {1, -1}, {0, -1}, {-1, -1}};
    if (x <= -1 || N <= x || y <= -1 || N <= y)
        return 0;
    if (dir == -1)
    {
        if (table[y][x] != NO)
            return 0;
        for (i = 0; i < 8; i++)
            check += isOk(player, y + dirLib[i][0], x + dirLib[i][1], 0, i);
        return check;
    }
    if (table[y][x] == player && flag)
        return 1;
    if (table[y][x] != target) // y, xへの範囲外値代入を防ぐため上のreturn 0には統合しない 自分かNOがいたら0を返す
        return 0;
    else
        return isOk(player, y + dirLib[dir][0], x + dirLib[dir][1], 1, dir);
}
int canPut(char player)
{
    for (int i = 0; i < N; i++)
        for (int j = 0; j < N; j++)
            if (isOk(player, i, j, 0, -1))
                return 1;
    return 0;
}
void reversi(char player, int y1, int x1, int y2, int x2)
{
    if (y1 > y2)
    {
        int tmp = y2;
        y2 = y1;
        y1 = tmp;
        tmp = x2;
        x2 = x1;
        x1 = tmp;
    }
    if (x1 == x2)
    {
        for (int i = y1; i <= y2; i++)
            table[i][x1] = player;
        return;
    }
    if (x1 > x2)
    {
        int tmp = y2;
        y2 = y1;
        y1 = tmp;
        tmp = x2;
        x2 = x1;
        x1 = tmp;
    }
    for (int i = x1; i <= x2; i++)
        table[((y1 - y2) / (x1 - x2)) * i + ((x1 * y2 - x2 * y1) / (x1 - x2))][i] = player;
}
void set(char player, int y1, int x1, int y2, int x2, int flag, int dir)
{
    char target = player == P1 ? P2 : P1;
    int i, check = 0, dirLib[8][2] = {{-1, 0}, {-1, 1}, {0, 1}, {1, 1}, {1, 0}, {1, -1}, {0, -1}, {-1, -1}};
    if (x2 <= -1 || N <= x2 || y2 <= -1 || N <= y2)
        return;
    if (dir == -1)
    {
        for (i = 0; i < 8; i++)
            set(player, y1, x1, y1 + dirLib[i][0], x1 + dirLib[i][1], 0, i);
        return;
    }
    if (table[y2][x2] == player && flag)
    {
        reversi(player, y1, x1, y2, x2);
        return;
    }
    if (table[y2][x2] != target)
        return;
    else
        set(player, y1, x1, y2 + dirLib[dir][0], x2 + dirLib[dir][1], 1, dir);
}
```
</details>

# プログラム紹介
## 冒頭部分
<details><summary>折りたたんでます。</summary>

```c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define N 8
void init(void);
void show(void);
void put(char);
int check(void);
int over(void);
int isOk(char, int, int, int, int);
int canPut(char);
void set(char, int, int, int, int, int, int);
void reversi(char, int, int, int, int);
char table[N][N];
char P1 = 'O', P2 = 'X', NO = '-';
int turn = 0;
```
- stdio.h: scanf()のため
- stdlib.h: rand()のため
- time.h: srand()に(unsigned int)time(NULL)を渡すため。
- void init(void): 盤面をP1 = 'O', P2 = 'X', NO = '-'で初期化します。
- void show(void): 盤面をコンソール上に表示します。現在のターン数(turn)も表示します。
- void put(char): P1 = 'O' かP2 = 'X'を受け取りターン数(turn)に従って手番のプレイヤーが相手コマを一枚でも返すことのできる位置があるか確認してから置き位置の座標入力を要求した後、それが正しい(相手コマを一枚でも返すことのできる)位置か確認してから返すことのできるコマを全て返します。
- int check(void): 勝敗を判断します。盤面のマスが全て埋まってから呼ばれます。
- int over(void): ゲームの終了を判断します。盤面のマスが全て終わっているかを見ます。
- int isOk(char, int, int, int, int): 置き位置が正しいか(1枚でも返すことのできる位置か)判断します。上下左右斜めの合計8方向を直線的に探索します。再帰をとっています。
- int canPut(char): プレイヤーが一枚でも返すことのできる位置があるかを判断します。isOk()を盤面全体に行います。
- void set(char, int, int, int, int, int, int): 受け取った二つの二次元座標をもとに返すことのできるコマを全て返します。再帰をとっています。
- void reversi(char, int, int, int, int): 受け取った二つの二次元座標をつなぐ直線上のコマを全て返します。
- char table[N][N]: 盤面です。P1 = 'O', P2 = 'X', NO = '-'のみが格納されます。
- char P1 = 'O', P2 = 'X', NO = '-': コマたちです。何も置かれていないところにはNOを置きます。
- int turn = 0: ターン数です。
</details>

## int main(void)
<details><summary>折りたたんでます。</summary>

```c
int main(void)
{
    srand((unsigned int)time(NULL));
    init();
    show();
    while (over())
    {
        if (turn % 2 == 0)
            put(P1);
        else
            put(P2);
        show();
        turn++;
    }
    if (1 == check())
        printf("I am a winner!\n");
    else if (0 == check())
        printf("I am a loser...\n");
    else
        printf("I am not neither.");
    return 0;
}
```
- srand((unsigned int)time(NULL));: 時間をシードに乱数系を新しく生成します。
- init(), show(): 冒頭部分で紹介した通りです。
- while部分: over()が終了を判断するまで交互にput()を続けます。
- check()部分: check()に従い勝ち負けを出力します。
</details>

## void put(char player)
<details><summary>折りたたんでます。</summary>

```c
void put(char player)
{
    int x, y;
    if (!canPut(player))
    {
        show();
        if (player == P1)
            printf("<---------The places can be put didn't exitst! Turnover!----------->\n");
        return;
    }
    while (1)
    {
        if (player == P1)
            // scanf("%d %d", &y, &x);
            x = rand() % N, y = rand() % N;
        else
            y = rand() % N, x = rand() % N;
        if (isOk(player, y, x, 0, -1)) // 置き位置として正しいか(範囲内かつ一枚でも相手コマを返せるか)
            break;
        else if (player == P1)
        {
            show();
            printf("<---------The place is not correct! Put again!----------->\n");
        }
    }
    set(player, y, x, 0, 0, 0, -1);
}
```
- 
</details>

## int isOk(char player, int y, int x, int flag, int dir)
<details><summary>折りたたんでます。</summary>

```c
int isOk(char player, int y, int x, int flag, int dir) // 行 列　の順で座標置く
{
    char target = player == P1 ? P2 : P1;
    int i, check = 0, dirLib[8][2] = {{-1, 0}, {-1, 1}, {0, 1}, {1, 1}, {1, 0}, {1, -1}, {0, -1}, {-1, -1}};
    if (x <= -1 || N <= x || y <= -1 || N <= y)
        return 0;
    if (dir == -1)
    {
        if (table[y][x] != NO)
            return 0;
        for (i = 0; i < 8; i++)
            check += isOk(player, y + dirLib[i][0], x + dirLib[i][1], 0, i);
        return check;
    }
    if (table[y][x] == player && flag)
        return 1;
    if (table[y][x] != target) // y, xへの範囲外値代入を防ぐため上のreturn 0には統合しない 自分かNOがいたら0を返す
        return 0;
    else
        return isOk(player, y + dirLib[dir][0], x + dirLib[dir][1], 1, dir);
}
```
- 
</details>

## void set(char player, int y1, int x1, int y2, int x2, int flag, int dir)
<details><summary>折りたたんでます。</summary>

```c
void set(char player, int y1, int x1, int y2, int x2, int flag, int dir)
{
    char target = player == P1 ? P2 : P1;
    int i, check = 0, dirLib[8][2] = {{-1, 0}, {-1, 1}, {0, 1}, {1, 1}, {1, 0}, {1, -1}, {0, -1}, {-1, -1}};
    if (x2 <= -1 || N <= x2 || y2 <= -1 || N <= y2)
        return;
    if (dir == -1)
    {
        for (i = 0; i < 8; i++)
            set(player, y1, x1, y1 + dirLib[i][0], x1 + dirLib[i][1], 0, i);
        return;
    }
    if (table[y2][x2] == player && flag)
    {
        reversi(player, y1, x1, y2, x2);
        return;
    }
    if (table[y2][x2] != target)
        return;
    else
        set(player, y1, x1, y2 + dirLib[dir][0], x2 + dirLib[dir][1], 1, dir);
}
```
</details>

## int canPut(char player)
<details><summary>折りたたんでます。</summary>

```c
int canPut(char player)
{
    for (int i = 0; i < N; i++)
        for (int j = 0; j < N; j++)
            if (isOk(player, i, j, 0, -1))
                return 1;
    return 0;
}
```
- 
</details>

## void reversi(char player, int y1, int x1, int y2, int x2)
<details><summary>折りたたんでます。</summary>

```c
void reversi(char player, int y1, int x1, int y2, int x2)
{
    if (y1 > y2)
    {
        int tmp = y2;
        y2 = y1;
        y1 = tmp;
        tmp = x2;
        x2 = x1;
        x1 = tmp;
    }
    if (x1 == x2)
    {
        for (int i = y1; i <= y2; i++)
            table[i][x1] = player;
        return;
    }
    if (x1 > x2)
    {
        int tmp = y2;
        y2 = y1;
        y1 = tmp;
        tmp = x2;
        x2 = x1;
        x1 = tmp;
    }
    for (int i = x1; i <= x2; i++)
        table[((y1 - y2) / (x1 - x2)) * i + ((x1 * y2 - x2 * y1) / (x1 - x2))][i] = player;
}
```
- 
</details>

## int check(void), void show(void), void init(void), int over(void)
<details><summary>折りたたんでます。</summary>

```c
int check(void)
{
    int cnt = 0;
    for (int i = 0; i < N; i++)
        for (int j = 0; j < N; j++)
            if (table[i][j] == P1)
                cnt++;
    if (cnt == N * N / 2)
        return 2;
    else if (cnt > N * N / 2)
        return 1;
    else
        return 0;
}
void show(void)
{
    system("cls");
    printf("turn : %d\nO:P1 X:P2 \nplease input a coordinate. \nex : y x\n", turn);
    for (int i = 0; i < N; i++)
    {
        if (i == 0)
        {
            printf("  ");
            for (int j = 0; j < N; j++)
                printf("%d ", j);
            printf("\n");
        }
        for (int j = 0; j < N; j++)
        {
            if (j == 0)
                printf("%d ", i);
            printf("%c ", table[i][j]);
        }
        printf("\n");
    }
    printf("\n\n");
}
void init(void)
{
    for (int i = 0; i < N; i++)
        for (int j = 0; j < N; j++)
            table[i][j] = NO;
    table[N / 2][N / 2] = table[N / 2 - 1][N / 2 - 1] = P1;
    table[N / 2][N / 2 - 1] = table[N / 2 - 1][N / 2] = P2;
}
int over(void)
{
    int cnt = 0;
    for (int i = 0; i < N; i++)
        for (int j = 0; j < N; j++)
            if (table[i][j] == NO)
                cnt++;
    if (cnt == 0)
        return 0;
    else
        return 1;
}
```
- 
</details>


















# 振り返り
似たような処理が多く再帰で書けたらなと思っていたので完成出来てよかったです。力仕事で書いた部分をisOk()とset()のdir[][]だけに絞り込めました。このおかげで作業量をかなり削減出来ました。しかし読むとわかるのですが、isOk()とset()の行う探索処理は非常に似ています。(isOk()をコピペして書き換えたのがset()です。)引数と返り値が違ったりしたので分けたのですがもし統合出来たらさらに短くできるのでまた頑張ってみます！

# 募集
現在乱数でコマを設置しているのですが、弱すぎて人間が相手するとものたりない部分が目立ちます...強い置き位置の決定方法があれば教えてほしいです。m(__)m