[C言語] ブラックジャックをするプログラムの作成方法

C言語でブラックジャックゲームを作成するには、基本的なプログラミングスキルとゲームのルール理解が必要です。

まず、カードデッキを表現するために配列を使用し、カードのシャッフルや配布を行います。

次に、プレイヤーとディーラーの手札を管理するために構造体を活用します。

ゲームの進行はループを用いて、プレイヤーの選択に応じて手札を追加したり、勝敗を判定します。

最終的に、得点計算を行い、勝者を決定します。

このように、C言語の基本的な機能を駆使して、シンプルなブラックジャックゲームを実装できます。

この記事でわかること
  • ブラックジャックゲームの基本的なプログラム構造の理解
  • カードデッキの実装方法とシャッフルの仕組み
  • プレイヤーとディーラーのターンのロジック
  • ユーザーインターフェースの設計と実装
  • 複数プレイヤー対応やAIディーラーの応用例

目次から探す

基本的なプログラム構造

ブラックジャックをC言語で実装するためには、プログラムの基本的な構造を理解することが重要です。

このセクションでは、メイン関数の設計、ヘッダーファイルの利用、データ構造の定義について詳しく説明します。

メイン関数の設計

メイン関数はプログラムのエントリーポイントであり、ブラックジャックゲームの全体的な流れを制御します。

以下に、メイン関数の基本的な設計例を示します。

#include <stdio.h>
// メイン関数
int main() {
    // ゲームの初期化
    initializeGame();
    // ゲームのループ
    while (!isGameOver()) {
        // プレイヤーのターン
        playerTurn();
        // ディーラーのターン
        dealerTurn();
        // 勝敗の判定
        determineWinner();
    }
    // ゲームの終了処理
    finalizeGame();
    return 0;
}

この例では、ゲームの初期化、プレイヤーとディーラーのターン、勝敗の判定、そしてゲームの終了処理を行う関数を呼び出しています。

これにより、ゲームの流れを簡潔に表現できます。

ヘッダーファイルの利用

ヘッダーファイルは、プログラムの構造を整理し、再利用性を高めるために使用されます。

ブラックジャックのプログラムでは、以下のようなヘッダーファイルを作成することが考えられます。

// blackjack.h
#ifndef BLACKJACK_H
#define BLACKJACK_H
// ゲームの初期化
void initializeGame();
// プレイヤーのターン
void playerTurn();
// ディーラーのターン
void dealerTurn();
// 勝敗の判定
void determineWinner();
// ゲームの終了処理
void finalizeGame();
#endif // BLACKJACK_H

このヘッダーファイルでは、ブラックジャックゲームに必要な関数のプロトタイプを宣言しています。

これにより、他のソースファイルからこれらの関数を利用することができます。

データ構造の定義

ブラックジャックゲームでは、カードやデッキ、プレイヤーの手札などを表現するためのデータ構造が必要です。

以下に、基本的なデータ構造の例を示します。

// カードの構造体
typedef struct {
    int rank; // カードのランク (1-13)
    char suit; // カードのスート ('H', 'D', 'C', 'S')
} Card;
// デッキの構造体
typedef struct {
    Card cards[52]; // 52枚のカード
    int top; // デッキの一番上のカードのインデックス
} Deck;
// プレイヤーの手札の構造体
typedef struct {
    Card hand[11]; // 最大11枚のカード
    int numCards; // 手札の枚数
} Hand;

この例では、カード、デッキ、プレイヤーの手札を表現するための構造体を定義しています。

これらのデータ構造を使用することで、ゲームの状態を管理しやすくなります。

カードデッキの実装

ブラックジャックゲームを実装する上で、カードデッキの管理は重要な要素です。

このセクションでは、カードの表現方法、デッキの初期化、カードのシャッフルについて詳しく説明します。

カードの表現方法

カードをプログラムで表現するためには、ランクとスート(絵柄)を持つ構造体を使用します。

以下に、カードの表現方法の例を示します。

// カードの構造体
typedef struct {
    int rank; // カードのランク (1-13)
    char suit; // カードのスート ('H', 'D', 'C', 'S')
} Card;

この構造体では、rankはカードのランクを表し、suitはカードのスートを表します。

ランクは1から13までの整数で、スートは’H'(ハート)、’D'(ダイヤ)、’C'(クラブ)、’S'(スペード)のいずれかの文字で表現します。

デッキの初期化

デッキを初期化するには、52枚のカードを生成し、それぞれのランクとスートを設定します。

以下に、デッキの初期化の例を示します。

#include <stdio.h>
// デッキの構造体
typedef struct {
    Card cards[52]; // 52枚のカード
    int top; // デッキの一番上のカードのインデックス
} Deck;
// デッキの初期化関数
void initializeDeck(Deck *deck) {
    char suits[] = {'H', 'D', 'C', 'S'};
    int index = 0;
    for (int s = 0; s < 4; s++) {
        for (int r = 1; r <= 13; r++) {
            deck->cards[index].rank = r;
            deck->cards[index].suit = suits[s];
            index++;
        }
    }
    deck->top = 0; // デッキの一番上を初期化
}

この関数では、4つのスートそれぞれに対して1から13までのランクを持つカードを生成し、デッキに追加しています。

カードのシャッフル

デッキをシャッフルすることで、ゲームの公平性を保ちます。

以下に、デッキのシャッフルの例を示します。

#include <stdlib.h>
#include <time.h>
// デッキのシャッフル関数
void shuffleDeck(Deck *deck) {
    srand(time(NULL)); // 乱数の種を初期化
    for (int i = 0; i < 52; i++) {
        int j = rand() % 52; // ランダムなインデックスを生成
        // カードを交換
        Card temp = deck->cards[i];
        deck->cards[i] = deck->cards[j];
        deck->cards[j] = temp;
    }
}

この関数では、srandを使用して乱数の種を初期化し、randを使用してランダムなインデックスを生成してカードを交換しています。

これにより、デッキがランダムにシャッフルされます。

以上の手順で、カードデッキの実装が完了します。

これにより、ゲームの開始時にデッキを初期化し、シャッフルすることができます。

ゲームのロジック

ブラックジャックゲームのロジックは、プレイヤーとディーラーのターン、ヒットとスタンドの選択、そして勝敗の判定から成り立っています。

このセクションでは、それぞれの要素について詳しく説明します。

プレイヤーとディーラーのターン

ブラックジャックでは、プレイヤーとディーラーが交互にカードを引くターンがあります。

プレイヤーは自分の手札の合計が21を超えないようにカードを引き、ディーラーは特定の条件に従ってカードを引きます。

// プレイヤーのターン
void playerTurn(Deck *deck, Hand *playerHand) {
    char choice;
    do {
        printf("現在の手札の合計: %d\n", calculateHandValue(playerHand));
        printf("ヒット(h)またはスタンド(s)を選択してください: ");
        scanf(" %c", &choice);
        if (choice == 'h') {
            drawCard(deck, playerHand);
        }
    } while (choice == 'h' && calculateHandValue(playerHand) <= 21);
}
// ディーラーのターン
void dealerTurn(Deck *deck, Hand *dealerHand) {
    while (calculateHandValue(dealerHand) < 17) {
        drawCard(deck, dealerHand);
    }
}

プレイヤーはヒットを選択する限りカードを引き続け、ディーラーは手札の合計が17以上になるまでカードを引きます。

ヒットとスタンドの実装

ヒットとスタンドは、プレイヤーがカードを引くかどうかを決定するための選択肢です。

ヒットを選択するとカードを引き、スタンドを選択するとターンを終了します。

// カードを引く関数
void drawCard(Deck *deck, Hand *hand) {
    if (deck->top < 52) {
        hand->hand[hand->numCards] = deck->cards[deck->top];
        hand->numCards++;
        deck->top++;
    }
}
// 手札の合計を計算する関数
int calculateHandValue(Hand *hand) {
    int total = 0;
    int aces = 0;
    for (int i = 0; i < hand->numCards; i++) {
        int rank = hand->hand[i].rank;
        if (rank > 10) {
            total += 10;
        } else if (rank == 1) {
            total += 11;
            aces++;
        } else {
            total += rank;
        }
    }
    while (total > 21 && aces > 0) {
        total -= 10;
        aces--;
    }
    return total;
}

このコードでは、drawCard関数でデッキからカードを引き、calculateHandValue関数で手札の合計を計算します。

エースは11または1として計算され、手札の合計が21を超えないように調整されます。

勝敗の判定

ゲームの最後に、プレイヤーとディーラーの手札の合計を比較して勝敗を判定します。

// 勝敗の判定関数
void determineWinner(Hand *playerHand, Hand *dealerHand) {
    int playerTotal = calculateHandValue(playerHand);
    int dealerTotal = calculateHandValue(dealerHand);
    printf("プレイヤーの合計: %d\n", playerTotal);
    printf("ディーラーの合計: %d\n", dealerTotal);
    if (playerTotal > 21) {
        printf("プレイヤーはバストしました。ディーラーの勝ちです。\n");
    } else if (dealerTotal > 21 || playerTotal > dealerTotal) {
        printf("プレイヤーの勝ちです!\n");
    } else if (playerTotal < dealerTotal) {
        printf("ディーラーの勝ちです。\n");
    } else {
        printf("引き分けです。\n");
    }
}

この関数では、プレイヤーとディーラーの手札の合計を比較し、バスト(21を超えること)や合計値に基づいて勝敗を判定します。

これにより、ゲームの結果を決定します。

ユーザーインターフェース

ブラックジャックゲームのユーザーインターフェースは、プレイヤーがゲームを楽しむための重要な要素です。

このセクションでは、コンソール出力のデザイン、ユーザー入力の処理、ゲームの進行表示について詳しく説明します。

コンソール出力のデザイン

コンソール出力は、ゲームの状態やプレイヤーの手札を視覚的に表示するために使用されます。

以下に、コンソール出力のデザイン例を示します。

// 手札を表示する関数
void displayHand(Hand *hand, const char *owner) {
    printf("%sの手札:\n", owner);
    for (int i = 0; i < hand->numCards; i++) {
        printf(" %d%c", hand->hand[i].rank, hand->hand[i].suit);
    }
    printf("\n合計: %d\n", calculateHandValue(hand));
}

この関数では、プレイヤーまたはディーラーの手札を表示し、各カードのランクとスートを出力します。

また、手札の合計も表示します。

これにより、プレイヤーは現在の手札の状況を把握できます。

ユーザー入力の処理

ユーザー入力は、プレイヤーがヒットやスタンドを選択する際に必要です。

以下に、ユーザー入力の処理例を示します。

// ユーザー入力を取得する関数
char getUserInput() {
    char input;
    printf("ヒット(h)またはスタンド(s)を選択してください: ");
    scanf(" %c", &input);
    return input;
}

この関数では、プレイヤーにヒットまたはスタンドの選択を促し、入力された文字を返します。

入力の際には、スペースを無視するためにscanfのフォーマット文字列にスペースを含めています。

ゲームの進行表示

ゲームの進行をプレイヤーにわかりやすく表示することは、ゲームの楽しさを向上させます。

以下に、ゲームの進行表示の例を示します。

// ゲームの進行を表示する関数
void displayGameProgress(Hand *playerHand, Hand *dealerHand) {
    printf("\n--- ゲームの進行 ---\n");
    displayHand(playerHand, "プレイヤー");
    displayHand(dealerHand, "ディーラー");
    printf("-------------------\n");
}

この関数では、プレイヤーとディーラーの手札を表示し、ゲームの進行状況を視覚的に示します。

これにより、プレイヤーはゲームの状態を常に把握することができます。

以上の要素を組み合わせることで、ユーザーインターフェースを通じてプレイヤーにわかりやすくゲームを提供することができます。

これにより、プレイヤーはゲームをより楽しむことができるでしょう。

完成したプログラム

ここでは、これまでに説明した要素を組み合わせて、ブラックジャックゲームの完成したプログラムを紹介します。

このプログラムは、コンソール上で動作するシンプルなブラックジャックゲームです。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// カードの構造体
typedef struct {
    int rank; // カードのランク (1-13)
    char suit; // カードのスート ('H', 'D', 'C', 'S')
} Card;
// デッキの構造体
typedef struct {
    Card cards[52]; // 52枚のカード
    int top; // デッキの一番上のカードのインデックス
} Deck;
// プレイヤーの手札の構造体
typedef struct {
    Card hand[11]; // 最大11枚のカード
    int numCards; // 手札の枚数
} Hand;
// デッキの初期化関数
void initializeDeck(Deck *deck) {
    char suits[] = {'H', 'D', 'C', 'S'};
    int index = 0;
    for (int s = 0; s < 4; s++) {
        for (int r = 1; r <= 13; r++) {
            deck->cards[index].rank = r;
            deck->cards[index].suit = suits[s];
            index++;
        }
    }
    deck->top = 0; // デッキの一番上を初期化
}
// デッキのシャッフル関数
void shuffleDeck(Deck *deck) {
    srand(time(NULL)); // 乱数の種を初期化
    for (int i = 0; i < 52; i++) {
        int j = rand() % 52; // ランダムなインデックスを生成
        // カードを交換
        Card temp = deck->cards[i];
        deck->cards[i] = deck->cards[j];
        deck->cards[j] = temp;
    }
}
// カードを引く関数
void drawCard(Deck *deck, Hand *hand) {
    if (deck->top < 52) {
        hand->hand[hand->numCards] = deck->cards[deck->top];
        hand->numCards++;
        deck->top++;
    }
}
// 手札の合計を計算する関数
int calculateHandValue(Hand *hand) {
    int total = 0;
    int aces = 0;
    for (int i = 0; i < hand->numCards; i++) {
        int rank = hand->hand[i].rank;
        if (rank > 10) {
            total += 10;
        } else if (rank == 1) {
            total += 11;
            aces++;
        } else {
            total += rank;
        }
    }
    while (total > 21 && aces > 0) {
        total -= 10;
        aces--;
    }
    return total;
}
// 手札を表示する関数
void displayHand(Hand *hand, const char *owner) {
    printf("%sの手札:\n", owner);
    for (int i = 0; i < hand->numCards; i++) {
        printf(" %d%c", hand->hand[i].rank, hand->hand[i].suit);
    }
    printf("\n合計: %d\n", calculateHandValue(hand));
}
// ユーザー入力を取得する関数
char getUserInput() {
    char input;
    printf("ヒット(h)またはスタンド(s)を選択してください: ");
    scanf(" %c", &input);
    return input;
}
// プレイヤーのターン
void playerTurn(Deck *deck, Hand *playerHand) {
    char choice;
    do {
        displayHand(playerHand, "プレイヤー");
        choice = getUserInput();
        if (choice == 'h') {
            drawCard(deck, playerHand);
        }
    } while (choice == 'h' && calculateHandValue(playerHand) <= 21);
}
// ディーラーのターン
void dealerTurn(Deck *deck, Hand *dealerHand) {
    while (calculateHandValue(dealerHand) < 17) {
        drawCard(deck, dealerHand);
    }
    displayHand(dealerHand, "ディーラー");
}
// 勝敗の判定関数
void determineWinner(Hand *playerHand, Hand *dealerHand) {
    int playerTotal = calculateHandValue(playerHand);
    int dealerTotal = calculateHandValue(dealerHand);
    printf("プレイヤーの合計: %d\n", playerTotal);
    printf("ディーラーの合計: %d\n", dealerTotal);
    if (playerTotal > 21) {
        printf("プレイヤーはバストしました。ディーラーの勝ちです。\n");
    } else if (dealerTotal > 21 || playerTotal > dealerTotal) {
        printf("プレイヤーの勝ちです!\n");
    } else if (playerTotal < dealerTotal) {
        printf("ディーラーの勝ちです。\n");
    } else {
        printf("引き分けです。\n");
    }
}
// メイン関数
int main() {
    Deck deck;
    Hand playerHand = { .numCards = 0 };
    Hand dealerHand = { .numCards = 0 };
    initializeDeck(&deck);
    shuffleDeck(&deck);
    // プレイヤーとディーラーに最初の2枚を配る
    drawCard(&deck, &playerHand);
    drawCard(&deck, &playerHand);
    drawCard(&deck, &dealerHand);
    drawCard(&deck, &dealerHand);
    // プレイヤーのターン
    playerTurn(&deck, &playerHand);
    // ディーラーのターン
    dealerTurn(&deck, &dealerHand);
    // 勝敗の判定
    determineWinner(&playerHand, &dealerHand);
    return 0;
}

プログラムの実行例

プレイヤーの手札:
 2S 8H
合計: 10
ヒット(h)またはスタンド(s)を選択してください: h
プレイヤーの手札:
 2S 8H 4C
合計: 14
ヒット(h)またはスタンド(s)を選択してください: h
プレイヤーの手札:
 2S 8H 4C 1S
合計: 15
ヒット(h)またはスタンド(s)を選択してください: h
プレイヤーの手札:
 2S 8H 4C 1S 5D
合計: 20
ヒット(h)またはスタンド(s)を選択してください: s
ディーラーの手札:
 12S 7H
合計: 17
プレイヤーの合計: 20
ディーラーの合計: 17
プレイヤーの勝ちです!

このプログラムは、プレイヤーとディーラーにカードを配り、プレイヤーがヒットまたはスタンドを選択し、ディーラーが特定の条件に従ってカードを引きます。

最終的に、手札の合計を比較して勝敗を判定します。

コンソール上でのシンプルなブラックジャックゲームを楽しむことができます。

応用例

ブラックジャックゲームの基本的な実装を理解したら、さらに機能を拡張してみましょう。

ここでは、複数プレイヤー対応、グラフィカルユーザーインターフェースの追加、AIディーラーの実装についての応用例を紹介します。

複数プレイヤー対応

複数のプレイヤーが参加できるようにすることで、ゲームの楽しさを増すことができます。

以下に、複数プレイヤー対応のための基本的な考え方を示します。

  • プレイヤーの管理: プレイヤーを配列やリストで管理し、それぞれの手札を追跡します。
  • ターンの制御: 各プレイヤーが順番にターンを行うように制御します。
  • 勝敗の判定: 各プレイヤーとディーラーの手札を比較し、それぞれの勝敗を判定します。
#define MAX_PLAYERS 4
typedef struct {
    Hand hands[MAX_PLAYERS];
    int numPlayers;
} Game;
// プレイヤーのターンを管理する関数
void playerTurns(Deck *deck, Game *game) {
    for (int i = 0; i < game->numPlayers; i++) {
        printf("プレイヤー%dのターン\n", i + 1);
        playerTurn(deck, &game->hands[i]);
    }
}

グラフィカルユーザーインターフェースの追加

コンソールベースのゲームをGUIに拡張することで、視覚的に魅力的なゲームを作成できます。

以下は、GUIを追加するための基本的な考え方です。

  • GUIライブラリの選択: SDLやQtなどのライブラリを使用して、ウィンドウやボタンを作成します。
  • イベント処理: ユーザーの入力をGUIイベントとして処理し、ゲームの状態を更新します。
  • グラフィックの描画: カードやテーブルのグラフィックを描画し、ゲームの進行を視覚的に表示します。

AIディーラーの実装

AIディーラーを実装することで、よりリアルなゲーム体験を提供できます。

以下は、AIディーラーを実装するための基本的な考え方です。

  • 戦略の定義: ディーラーの行動を決定するための戦略を定義します。

例えば、特定の手札の合計に達するまでヒットを続けるなど。

  • 確率の計算: ディーラーが次に引くカードの確率を計算し、最適な行動を選択します。
  • 動的な行動: プレイヤーの手札に応じてディーラーの行動を動的に変更します。
// AIディーラーのターン
void aiDealerTurn(Deck *deck, Hand *dealerHand) {
    while (calculateHandValue(dealerHand) < 17) {
        drawCard(deck, dealerHand);
    }
    // 追加の戦略をここに実装
}

これらの応用例を実装することで、ブラックジャックゲームをより複雑で魅力的なものにすることができます。

各機能を段階的に追加し、ゲームの完成度を高めていきましょう。

よくある質問

プログラムがクラッシュするのはなぜ?

プログラムがクラッシュする原因はさまざまですが、一般的な原因として以下の点が考えられます。

  • メモリの不正アクセス: 配列の範囲外にアクセスしている可能性があります。

例:array[-1]array[100]のようなアクセス。

  • NULLポインタの参照: ポインタがNULLであるにもかかわらず、そのポインタを参照している場合。
  • 無限ループ: ループの終了条件が正しく設定されていないため、無限にループしている可能性があります。

これらの問題を解決するためには、デバッグツールを使用してクラッシュの原因を特定し、コードを修正することが重要です。

カードのシャッフルがうまくいかないのはなぜ?

カードのシャッフルがうまくいかない場合、以下の点を確認してください。

  • 乱数の初期化: srand(time(NULL))を使用して乱数の種を初期化しているか確認してください。

これを行わないと、毎回同じ順序でシャッフルされる可能性があります。

  • シャッフルアルゴリズム: シャッフルのアルゴリズムが正しく実装されているか確認してください。

カードの交換が正しく行われているかをチェックします。

これらの点を確認し、必要に応じてシャッフルのロジックを修正してください。

勝敗判定が正しく動作しないのはなぜ?

勝敗判定が正しく動作しない場合、以下の点を確認してください。

  • 手札の合計計算: calculateHandValue関数が正しく手札の合計を計算しているか確認してください。

特にエースの扱いに注意が必要です。

  • 条件分岐の誤り: 勝敗を判定する条件分岐が正しく設定されているか確認してください。

例:if文の条件が正しいかどうか。

これらの点を確認し、必要に応じて判定ロジックを修正してください。

まとめ

ブラックジャックゲームのC言語による実装を通じて、基本的なプログラム構造から応用例までを学びました。

プログラムのクラッシュやシャッフルの問題、勝敗判定の誤りなど、よくある問題の解決方法も理解できたはずです。

これを機に、さらに複雑な機能を追加したり、他のゲームを実装したりして、プログラミングスキルを向上させましょう。

当サイトはリンクフリーです。出典元を明記していただければ、ご自由に引用していただいて構いません。

関連カテゴリーから探す

  • URLをコピーしました!
目次から探す