デザパタ(ストラテジー編) | 不惑のクワガタ日記

不惑のクワガタ日記

はじめた当初はクワガタの日記だったのですが、、、
現在は人工知能と戯れる日記になってます。

(私)
簡単なマルバツゲームをc言語で作成してください。

(chatGPT)
以下はC言語で簡単なマルバツゲームを作成する例です。この例では、2人のプレイヤーが交互に入力し、勝者が決まるまでゲームを続けます。

・・・

(私)
途中できれちゃいましたね、、、冗長なのでしょうビット演算子を使用して書き換えてください。

(chatGPT)
ビット演算子を使用して、条件文をより短く書くことができます。以下は、先ほどのプログラムにビット演算子を使って書き直した例です。

・・・

(私)
また切れちゃいましたね、、、2次元配列のboardは、1次元配列にしましょう。
あと draw_board() の中の枠を出力するところも割愛しましょう。

(chatGPT)
おっしゃる通りです。以下は、2次元配列を1次元配列にし、盤面の描画に不要な枠を削除した、改善版のプログラムです。

#include <stdio.h>

char board[9] = { // 1次元配列で盤面を作成
    '1', '2', '3',
    '4', '5', '6',
    '7', '8', '9'
};

int player = 1; // 最初のプレイヤーは1
int choice;
char mark;

void draw_board() {
    // 盤面を表示する関数
    printf("\n");
    printf(" %c | %c | %c \n", board[0], board[1], board[2]);
    printf("---+---+---\n");
    printf(" %c | %c | %c \n", board[3], board[4], board[5]);
    printf("---+---+---\n");
    printf(" %c | %c | %c \n", board[6], board[7], board[8]);
    printf("\n");
}

void mark_board(char mark) {
    // マスに印を付ける関数
    board[choice - 1] = mark;
}

int check_win() {
    // 勝者をチェックする関数
    int row, col;
    char player_mark = (player == 1) ? 'X' : 'O';
    
    // 行をチェック
    for (row = 0; row < 3; row++) {
        if ((board[row * 3] & board[row * 3 + 1] & board[row * 3 + 2]) == player_mark) {
            return 1;
        }
    }
    
    // 列をチェック
    for (col = 0; col < 3; col++) {
        if ((board[col] & board[col + 3] & board[col + 6]) == player_mark) {
            return 1;
        }
    }
    
    // 斜めをチェック
    if ((board[0] & board[4] & board[8]) == player_mark) {
        return 1;
    }
    if ((board[2] & board[4] & board[6]) == player_mark) {
        return 1;
    }
    
    return 0;
}

int main() {
    int i;
    int row, col;
    
    draw_board();
    
    for (i = 1; i <= 9; i++) {
        player = (i % 2) ? 1 : 2; // プレイヤーを切り替える
        printf("Player %d, enter a number: ", player);
        scanf("%d", &choice);
        
        mark = (player == 1) ? 'X' : 'O'; // プレイヤーの印を設定する
        mark_board(mark);
        
        draw_board();
        
        if (check_win()) {
            printf("Player %d wins!\n", player);
            return 0;
        }
    }
    
    printf("Draw game.\n");
    
    return 0;
}

(私)
いいですね!
つぎは上記プログラムをc++にし、人vsCPUにしてください。
 

(chatGPT)

以下が、C++で人対CPUのマルバツゲームの例です。

#include <iostream>
#include <vector>
#include <random>

using namespace std;

vector<char> board(9, ' '); // 盤面を1次元のchar型vectorで表現する
const int player = 1;
const int computer = 2;
char player_mark = 'X';
char computer_mark = 'O';

void draw_board() {
    // 盤面を表示する関数
    cout << "\n " << board[0] << " | " << board[1] << " | " << board[2] << endl;
    cout << "---+---+---" << endl;
    cout << " " << board[3] << " | " << board[4] << " | " << board[5] << endl;
    cout << "---+---+---" << endl;
    cout << " " << board[6] << " | " << board[7] << " | " << board[8] << endl << endl;
}

int get_player_choice() {
    // プレイヤーにマスの選択を尋ねる関数
    int choice;
    while (true) {
        cout << "Enter a number (1-9): ";
        cin >> choice;
        if (choice < 1 || choice > 9) {
            cout << "Invalid choice. Please try again." << endl;
        } else if (board[choice - 1] != ' ') {
            cout << "That space is already occupied. Please try again." << endl;
        } else {
            return choice - 1;
        }
    }
}

int get_computer_choice() {
    // コンピュータがマスを選択する関数
    int choice;
    vector<int> choices;
    for (int i = 0; i < 9; i++) {
        if (board[i] == ' ') {
            choices.push_back(i);
        }
    }
    if (choices.size() > 0) {
        default_random_engine engine(random_device{}());
        uniform_int_distribution<int> distribution(0, choices.size() - 1);
        choice = choices[distribution(engine)];
        return choice;
    } else {
        return -1;
    }
}

int check_win(int player) {
    // 勝者をチェックする関数
    char mark = (player == 1) ? player_mark : computer_mark;
    for (int i = 0; i < 3; i++) {
        if (board[i * 3] == mark && board[i * 3 + 1] == mark && board[i * 3 + 2] == mark) {
            return player;
        } else if (board[i] == mark && board[i + 3] == mark && board[i + 6] == mark) {
            return player;
        }
    }
    if (board[0] == mark && board[4] == mark && board[8] == mark) {
        return player;
    } else if (board[2] == mark && board[4] == mark && board[6] == mark) {
        return player;
    }
    return 0;
}

int main() {
    int turn = 1;
    int winner = 0;
    int choice;
    
    cout << "Welcome to Tic-Tac-Toe!" << endl << endl;
    cout << "Player 1 is X." << endl;
    
    while (turn)
    {
        if (turn % 2 == 1) {
            // プレイヤーのターン
            draw_board();
            cout << "Player 1, your turn." << endl;
            choice = get_player_choice();
            board[choice] = player_mark;
            winner = check_win(player);
        } else {
            // コンピュータのターン
            draw_board();
            cout << "Computer's turn." << endl;
            choice = get_computer_choice();
            if (choice >= 0) {
                board[choice] = computer_mark;
            }
            winner = check_win(computer);
        }
        
        turn++;
        if (winner != 0) {
            draw_board();
            if (winner == player) {
                cout << "Congratulations, you win!" << endl;
            } else {
                cout << "The computer wins." << endl;
            }
            break;
        } else if (turn > 9) {
            draw_board();
            cout << "It's a tie." << endl;
            break;
        }
    }
    
    return 0;
}
このプログラムでは、vector を使用して1次元の盤面を表現しています。get_player_choice() 関数では、プレイヤーに1から9までの数字を入力させ、その数字が選択可能かどうかをチェックして、選択可能な場合にはインデックスに変換して返します。get_computer_choice() 関数では、空きマスを検索して、ランダムに選択します。check_win() 関数では、行、列、斜めのいずれかに同じマークが揃っているかどうかをチェックして、勝者がいる場合にはそのプレイヤーの番号を返します。

また、このプログラムでは、マークの種類やプレイヤーの番号など、いくつかの定数を使用しています。これにより、プログラム全体で同じ定数を使用することができ、プログラムをより読みやすく、保守性が高くなります。


(私)
いいですね!次は上記プログラムをベースに、デザインパターン:ストラテジーを組んでCPUの思考方法を将来的に変更できるように改造してください。

(chatGPT)
了解しました。以下が、ストラテジーパターンを使用してCPUの思考方法を柔軟に変更できるようにしたプログラムです。

#include <iostream>
#include <vector>
#include <random>
using namespace std;

// 盤面を表すベクター
vector<char> board(9, ' ');

// プレイヤーのマークとコンピュータのマーク
const char player_mark = 'X';
const char computer_mark = 'O';

// ヒューマンプレイヤーを表すクラス
class HumanPlayer {
public:
    // 人間が次の手を選ぶためのメソッド
    int get_choice() {
        int choice;
        while (true) {
            cout << "Enter a number (1-9): ";
            cin >> choice;
            if (choice < 1 || choice > 9 || board[choice - 1] != ' ') {
                cout << "Invalid choice. Please try again." << endl;
            } else {
                return choice - 1;
            }
        }
    }
};

// コンピュータの戦略を表す抽象クラス
class Strategy {
public:
    // 次の手を選ぶためのメソッド
    virtual int get_choice(const vector<char>& board, char mark) = 0;
};

// ランダムな戦略を表すクラス
class RandomStrategy : public Strategy {
public:
    // 次の手をランダムに選ぶメソッド
    int get_choice(const vector<char>& board, char mark) {
        vector<int> choices;
        for (int i = 0; i < 9; i++) {
            if (board[i] == ' ') {
                choices.push_back(i);
            }
        }
        if (choices.size() > 0) {
            uniform_int_distribution<int> distribution(0, choices.size() - 1);
            return choices[distribution(random_engine)];
        } else {
            return -1;
        }
    }
private:
    // 乱数を生成するためのエンジン
    default_random_engine random_engine{random_device{}()};
};

// スマートな戦略を表すクラス
class SmartStrategy : public RandomStrategy {
public:
    // スマートなアルゴリズムを用いて次の手を選ぶメソッド
    int get_choice(const vector<char>& board, char mark) {
        // 自分が勝てる手があるかどうかをチェック
        for (int i = 0; i < 9; i++) {
            if (board[i] == ' ') {
                vector<char> new_board(board);
                new_board[i] = mark;
                if (check_win(new_board, mark)) {
                    return i;
                }
            }
        }
        // 相手が勝てる手を防ぐための手があるかどうかをチェック
        for (int i = 0; i < 9; i++) {
            if (board[i] == ' ') {
                vector<char> new_board(board);
                new_board[i] = (mark == 'X') ? 'O' : 'X';
                if (check_win(new_board, (mark == 'X') ? 'O' : 'X')) {
                    return i;
                }
            }
        }
        // 上記のいずれの手もない場合は、ランダムな手を選ぶ
        return RandomStrategy::get_choice(board, mark);
    }
private:
    // 自分が勝つ手があるかどうかをチェックするためのメソッド
    bool check_win(const vector<char>& board, char mark) {
        for (int i = 0; i < 3; i++) {
            if (board[i * 3] == mark && board[i * 3 + 1] == mark && board[i * 3 + 2] == mark) {
                return true;
            } else if (board[i] == mark && board[i + 3] == mark && board[i + 6] == mark) {
                return true;
            }
        }
        if (board[0] == mark && board[4] == mark && board[8] == mark) {
            return true;
        } else if (board[2] == mark && board[4] == mark && board[6] == mark) {
            return true;
        }
        return false;
    }
};

// コンピュータを表すクラス
class Computer {
public:
    // コンピュータが次の手を選ぶためのメソッド
    Computer(Strategy& strategy) : strategy(strategy) {}
    int get_choice(const vector<char>& board, char mark) {
        return strategy.get_choice(board, mark);
    }
private:
    Strategy& strategy;
};

// 盤面を表示する関数
void draw_board() {
    cout << "\n " << board[0] << " | " << board[1] << " | " << board[2] << endl;
    cout << "---+---+---" << endl;
    cout << " " << board[3] << " | " << board[4] << " | " << board[5] << endl;
    cout << "---+---+---" << endl;
    cout << " " << board[6] << " | " << board[7] << " | " << board[8] << endl << endl;
}

// 勝敗をチェックする関数
int check_win(const vector<char>& board, char mark) {
    for (int i = 0; i < 3; i++) {
        if (board[i * 3] == mark && board[i * 3 + 1] == mark && board[i * 3 + 2] == mark) {
            return 1;
        } else if (board[i] == mark && board[i + 3] == mark && board[i + 6] == mark) {
            return 1;
        }
    }
    if (board[0] == mark && board[4] == mark && board[8] == mark) {
        return 1;
    } else if (board[2] == mark && board[4] == mark && board[6] == mark) {
        return 1;
    }
    return 0;
}

// メイン関数
int main() {
    // ヒューマンプレイヤーとコンピュータを作成
    HumanPlayer human_player;
    Computer computer_player(SmartStrategy());

    // ゲームの初期設定
    int turn = 1
    int winner = 0;
    int choice;
    
    cout << "Welcome to Tic-Tac-Toe!" << endl << endl;
    cout << "Player 1 is X." << endl;

    // ゲームを開始
    while (true) {
        // プレイヤーが交互に手を選ぶ
        if (turn % 2 == 1) {
            draw_board();
            cout << "Player 1, your turn." << endl;
            choice = human_player.get_choice();
            board[choice] = player_mark;
            winner = check_win(board, player_mark);
        } else {
            draw_board();
            cout << "Computer's turn." << endl;
            choice = computer_player.get_choice(board, computer_mark);
            board[choice] = computer_mark;
            winner = check_win(board, computer_mark);
        }
        
        // 勝敗がついた場合、終了
        turn++;
        if (winner) {
            draw_board();
            if (winner == 1) {
                cout << "Congratulations, you win!" << endl;
            } else {
                cout << "The computer wins." << endl;
            }
            break;
        } else if (turn > 9) {
            draw_board();
            cout << "It's a tie." << endl;
            break;
        }
    }
    
    return 0;
}
このプログラムでは、まず盤面を表す board ベクターを定義しました。HumanPlayer クラスと Strategy クラスを定義し、RandomStrategy クラスと SmartStrategy クラスを作成しました。Computer クラスは、Strategy クラスを使用して、次の手を選びます。draw_board() 関数は、盤面を表示するために使用されます。check_win() 関数は、勝敗をチェックするために使用されます。

メイン関数では、HumanPlayer クラスと Computer クラスを作成し、ゲームを開始します。交互に手を選び、勝敗がつくか、引き分けになるまで繰り返します。SmartStrategy クラスでは、より複雑なアルゴリズムを使用することで、CPUの強さを向上させています。また、プログラムは、ストラテジーパターンを使用しているため、将来的に異なる戦略を簡単に実装することができます。