🃏 カードゲーム - 解答と解説
課題3-1の要件
- トランプの基本的な処理を実装
- カードの生成とシャッフル
- 手札の管理
- 得点計算
1. プログラムの実装 (card_game.c)
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
typedef enum {
SPADES,
HEARTS,
DIAMONDS,
CLUBS,
SUIT_MAX
} Suit;
typedef struct {
Suit suit;
int number;
int is_dealt;
} Card;
typedef struct {
Card* cards;
int num_cards;
} Hand;
typedef struct {
Card cards[52];
int top;
} Deck;
void initializeDeck(Deck* deck);
void shuffleDeck(Deck* deck);
Card drawCard(Deck* deck);
void displayCard(const Card* card);
const char* getSuitName(Suit suit);
void initializeHand(Hand* hand);
void addCardToHand(Hand* hand, Card card);
void displayHand(const Hand* hand);
int calculateScore(const Hand* hand);
void freeHand(Hand* hand);
int main() {
system("chcp 65001");
srand((unsigned int)time(NULL));
Deck deck;
Hand player_hand;
initializeDeck(&deck);
shuffleDeck(&deck);
initializeHand(&player_hand);
for (int i = 0; i < 5; i++) {
Card card = drawCard(&deck);
addCardToHand(&player_hand, card);
}
printf("あなたの手札:\n");
displayHand(&player_hand);
int score = calculateScore(&player_hand);
printf("\n得点: %d\n", score);
freeHand(&player_hand);
return 0;
}
void initializeDeck(Deck* deck) {
int index = 0;
for (int suit = 0; suit < SUIT_MAX; suit++) {
for (int number = 1; number <= 13; number++) {
deck->cards[index].suit = suit;
deck->cards[index].number = number;
deck->cards[index].is_dealt = 0;
index++;
}
}
deck->top = 0;
}
void shuffleDeck(Deck* deck) {
for (int i = 51; i > 0; i--) {
int j = rand() % (i + 1);
Card temp = deck->cards[i];
deck->cards[i] = deck->cards[j];
deck->cards[j] = temp;
}
}
Card drawCard(Deck* deck) {
Card card = deck->cards[deck->top];
deck->cards[deck->top].is_dealt = 1;
deck->top++;
return card;
}
const char* getSuitName(Suit suit) {
switch (suit) {
case SPADES: return "♠";
case HEARTS: return "♥";
case DIAMONDS: return "♦";
case CLUBS: return "♣";
default: return "?";
}
}
void displayCard(const Card* card) {
const char* number_str;
switch (card->number) {
case 1: number_str = "A"; break;
case 11: number_str = "J"; break;
case 12: number_str = "Q"; break;
case 13: number_str = "K"; break;
default:
printf("%d", card->number);
number_str = "";
}
printf("%s%s ", getSuitName(card->suit), number_str);
}
void initializeHand(Hand* hand) {
hand->cards = NULL;
hand->num_cards = 0;
}
void addCardToHand(Hand* hand, Card card) {
hand->num_cards++;
hand->cards = (Card*)realloc(hand->cards, sizeof(Card) * hand->num_cards);
hand->cards[hand->num_cards - 1] = card;
}
void displayHand(const Hand* hand) {
for (int i = 0; i < hand->num_cards; i++) {
displayCard(&hand->cards[i]);
}
printf("\n");
}
int calculateScore(const Hand* hand) {
int score = 0;
for (int i = 0; i < hand->num_cards; i++) {
if (hand->cards[i].number > 10) score += 10;
else score += hand->cards[i].number;
}
return score;
}
void freeHand(Hand* hand) {
if (hand->cards != NULL) {
free(hand->cards);
hand->cards = NULL;
}
hand->num_cards = 0;
}
2. プログラムの解説
2.1 データ構造
Suit
(列挙型)
- カードのスート(マーク)を表現
- SPADES, HEARTS, DIAMONDS, CLUBSの4種類
Card
(構造体)
- suit: カードのスート
- number: カードの数字(1-13)
- is_dealt: 配布済みフラグ
Hand
(構造体)
- cards: カードの動的配列
- num_cards: 手札の枚数
Deck
(構造体)
- cards: 52枚のカード配列
- top: 次に引くカードの位置
2.2 主要な関数(続き)
- 表示機能
displayCard()
: カードの表示
getSuitName()
: スート記号の取得
displayHand()
: 手札全体の表示
- スコア計算
calculateScore()
: 手札の得点計算
3. メモリ管理のポイント
3.1 動的メモリ割り当て
- 手札の管理
- カードを追加するたびにrealloc()でメモリを再割り当て
- 不要になった時点でfree()でメモリを解放
- NULLチェックによる安全性確保
- メモリリーク防止
- 全てのメモリ確保に対応する解放処理
- エラー時の適切なメモリ解放
4. 改良のポイント
4.1 基本的な改良案
- ゲームルールの追加
- ブラックジャックルールの実装
- ポーカーの役判定
- 複数プレイヤーの対応
- 表示の改善
- カードのアスキーアート表示
- 色付きの表示(ハートとダイヤは赤色など)
- UIの改善
- エラー処理の強化
- メモリ確保失敗時の対応
- 不正な入力値のチェック
- デッキ枚数超過のチェック
4.2 発展的な機能追加
- ゲームの拡張
- セーブ&ロード機能
- 複数のゲームモード
- 対戦履歴の記録
- AI対戦
- ネットワーク対戦
- マルチプレイヤー対応
- オンラインランキング
- チャット機能
5. よくある問題と解決策
5.1 メモリ関連の問題
- メモリリーク
- 手札の解放忘れ
- 部分的な解放漏れ
- エラー時の解放忘れ
- 不正なメモリアクセス
- 配列の範囲外アクセス
- 解放済みメモリの使用
- 未初期化メモリの参照
5.2 ロジックの問題
- カード管理の問題
- 重複したカードの配布
- 無効なカード値の生成
- デッキ枚数の不一致
- スコア計算の問題
- 特殊カード(A,J,Q,K)の扱い
- 役の判定ミス
- 計算式の誤り
6. テストのポイント
6.1 テストケース
- デッキ操作のテスト
- 初期化時の52枚確認
- シャッフル後の枚数確認
- カードの重複チェック
- 手札操作のテスト
- 追加・削除の動作確認
- メモリ管理の確認
- 上限値のテスト
- スコア計算のテスト