質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.04%

特定の配列のみシャッフルする方法を教えてほしいです。

受付中

回答 2

投稿

  • 評価
  • クリップ 1
  • VIEW 1,324

こんにちは
私は現在カードゲームを作成しています。
作成したいゲームの基本的な処理は作成できたのですが一か所どうすれば実現できるのかわからない処理があり困っています。
【ルール】
・場に出ているカードの絵柄か数字が手札のカードと同じだった場合、
一致したカードを捨てることができる。
・手札が0枚になったら勝ちとなる。
・手札枚数が51枚になり、カードを引こうとした場合プレイヤーの負けとする。
・山札が0枚になったときカードを引こうとした場合、最後に場に出したカードと手札以外のカードをシャッフルし再度山札を作る。

この最後のルールの処理を実現したいのですが特定の配列の値(手札と最後に場に出したカード値)を固定し、
その他の配列をシャッフルすれば可能なのではないかと思いました。
main関数のコメントアウトした箇所で再シャッフル用の関数(リバースデッキ関数)を呼び出してシャッフルしようとしたのですが、私の考え方が違うのかうまくいきませんでした。
どうすれば最後のルールの再シャッフル処理をできるのか知恵をお借りしたいです。

#define _CRT_SECURE_NO_WARNINGS//scanf警告解除用
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define CARD_MAX (52)//カード枚数
#define SAME_CARD (13)//同じ種類のカード枚数
#define INIT_HAND (5)//初手
#define TRUE (1)//正
#define FALSE (0)//負
#define YES (1)//イエス
#define NO (2)//ノー
#define SPADE (0)//スペード
#define CLUB (1)//クラブ
#define DIA (2)//ダイヤ
#define HEART (3)//ハート

/*構造体定義*/
typedef struct {

    unsigned int hand_cnt;//手札カウンタ
    unsigned char card_num[CARD_MAX];//カードの種類

}PLAYER_DATA;

/*変数宣言*/
unsigned char card_deck[CARD_MAX];//カードデッキ
unsigned int rest_cnt;//デッキ残数カウンタ
unsigned int turn_cnt;//ターンカウンタ

char number[CARD_MAX];//カード値格納用

/*関数プロトタイプ宣言*/
void init_card(void);
void draw_card(PLAYER_DATA *myhand);
void output_hand(PLAYER_DATA *myhand);
void cemetery_card(PLAYER_DATA* field, PLAYER_DATA* myhand, int *input_data);
void delete_hand(PLAYER_DATA *myhand, int *input_data);
int search_card(PLAYER_DATA *field, PLAYER_DATA *myhandint, int *input_data);
void rebirth_deck(PLAYER_DATA *field, PLAYER_DATA *myhand);
/********************************/
/* メイン関数          */
/********************************/
int main(int argc, char*argv[])
{
    static PLAYER_DATA myhand;//プレイヤーの手札構造体
    PLAYER_DATA field_card;//場のカード構造体

    int draw_cnt;//ドローカウンタ
    int input;//プレイヤー入力
    int return_num;//戻り値
    int yes_no;//Yes or No

    init_card();//デッキ初期化
    myhand.hand_cnt = 0;//手札枚数初期化
    field_card.hand_cnt = 0;//場のカード枚数初期化
    turn_cnt = 1;//ターンカウンタ初期化(開始時を1ターン目とする)

    /*カードを5枚ドロー*/
    for (draw_cnt = 0; draw_cnt < INIT_HAND; draw_cnt++) {
        draw_card(&myhand);
    }
    /*場に出すカードをドロー*/
    draw_card(&field_card);

    /*画面にカードを出力*/
    printf("******************************************************\n");
    printf("現在のターン:%dターン目\n", turn_cnt);
    printf("******************************************************\n");
    printf("場のカード:\n");
    output_hand(&field_card);
    printf("\nあなたの手札:\n");
    output_hand(&myhand);

    while (1) {
        /*************/
        /* 1ターン目 */
        /*************/
        printf("\n山札(残数) %d 枚\n", CARD_MAX - rest_cnt);
        printf("\n【ルール】\n");
        printf("場のカードと同じ数字か同じスートの\n");
        printf("カードを捨てることができます。\n");
        printf("\n");
        printf("カードを捨てますか?\n");
        printf("(1)Yes (2)No\n");
        scanf("%d", &yes_no);
        printf("\n");
        if (yes_no == YES) {//捨てる場合捨てるカードを選択
            printf("捨てるカード番号を入力してください:");
            scanf("%d", &input);
            return_num = search_card(&field_card, &myhand, &input);
            if (return_num == TRUE) {//フィールドのカード値または絵柄が一致した場合
                cemetery_card(&field_card, &myhand, &input);//card_dataの配列は[0~4]出力のため関数内で-1する
                delete_hand(&myhand, &input);
            }
            else if (return_num == FALSE) {
                printf("\n選んだ番号のカードは捨てられません。\n");
                printf("デッキからカードを一枚引きます。\n");

                //if (rest_cnt == CARD_MAX) {
                //rebirth_deck(&field_card, &myhand);
                //}

                draw_card(&myhand);

            }
        }
        else if (yes_no == NO) {//捨てない場合デッキからカードを引く
            draw_card(&myhand);
            if (myhand.hand_cnt == CARD_MAX) {//手札に52枚目を加えようとしたらバースト
                printf("\nカードがなくなりました。\n");
                printf("あなたの負けです。\n");
                printf("ゲームを終了します。\n");
                break;
            }
        }
        turn_cnt++;//カードを捨てたら1ターン目終了
        /****************/
        /*以下nターン目 */
        /****************/
        printf("******************************************************\n");
        printf("現在のターン:%dターン目\n", turn_cnt);
        printf("******************************************************\n");
        printf("場のカード:\n");
        output_hand(&field_card);
        printf("\nあなたの手札:\n");
        output_hand(&myhand);
        if (myhand.hand_cnt == 0) {//手札が0になったら
            printf("\nゲームクリアおめでとうございます。\n");
            printf("クリアするのに%dターンかかりました。\n", turn_cnt);
            printf("このまま終了します。\n");
            break;
        }
    }

    /*デバッグ用*/
#if 1
    int debag;
    scanf("%d", &debag);
#endif
    return 0;
}
/********************************/
/* デッキシャッフル(初期化)関数 */
/********************************/
void init_card(void) {

    int rnd;//ランダム値格納用変数
    int tmp;
    int arr_cnt;//配列カウンタ
    srand((unsigned)time(NULL));

    for (arr_cnt = 0; arr_cnt < CARD_MAX; arr_cnt++) {//カード値配列初期化
        number[arr_cnt] = arr_cnt;

    }

    for (rest_cnt = 0; rest_cnt< CARD_MAX; rest_cnt++) {//デッキ初期化

        card_deck[rest_cnt] = rest_cnt;

    }

    rest_cnt = 0;//初期残数0

    for (rest_cnt = 0; rest_cnt < CARD_MAX; rest_cnt++) {//デッキシャッフル

        rnd = rand() % (CARD_MAX);
        tmp = card_deck[rest_cnt];
        card_deck[rest_cnt] = card_deck[rnd];
        card_deck[rnd] = tmp;

    }

    rest_cnt = 0;
}

/********************************/
/* ドローカード関数                */
/********************************/
void draw_card(PLAYER_DATA *myhand) {

    myhand->card_num[myhand->hand_cnt] = card_deck[rest_cnt];

    myhand->hand_cnt++;//手札に一枚加える
    rest_cnt++;//デッキから1枚カードを抜く

}

/********************************/
/* 手札表示関数                  */
/********************************/
void output_hand(PLAYER_DATA *myhand)
{
    unsigned int myhnd_cnt;
    int select;
    int card;
    int suit;
    select = 0;

    //ドローしたカード枚数分画面に表示
    for (myhnd_cnt = 0; myhnd_cnt < myhand->hand_cnt; myhnd_cnt++) {
        select++;
        card = (int)(number[myhand->card_num[myhnd_cnt]]) % SAME_CARD + 1;
        suit = (int)(number[myhand->card_num[myhnd_cnt]])/ SAME_CARD;

        printf("(%d)%d", select, card);
        switch (suit) {

        case SPADE:
            printf("S");
            break;

        case CLUB:
            printf("C");
            break;

        case DIA:
            printf("D");
            break;

        case HEART:
            printf("H");
            break;

        }

    }

    printf("\n");
}

/********************************/
/* セメタリーカード関数          */
/********************************/
void cemetery_card(PLAYER_DATA *field, PLAYER_DATA *myhand, int *input_data)
{
    //セメタリーのカードを選択された手札で更新
    field->card_num[field->hand_cnt] = myhand->card_num[*input_data - 1];
    field->hand_cnt++;//フィールドのカード列をずらす

}

/********************************/
/* 手札削除関数                 */
/********************************/
void delete_hand(PLAYER_DATA *myhand, int *input_data)
{
    unsigned int input_cnt;

    //入力された選択肢のカードを起点に手札を一枚ずつずらす
    for (input_cnt = *input_data - 1; input_cnt < myhand->hand_cnt - 1; input_cnt++) {
        myhand->card_num[input_cnt] = myhand->card_num[input_cnt + 1];
    }
    myhand->hand_cnt--;//手札を一枚減らす
}

/********************************/
/* 手札検索関数                 */
/********************************/
int search_card(PLAYER_DATA *field, PLAYER_DATA *myhand,int *input_data)
{
    unsigned int search_cnt;
    int answer;
    int hand_card;
    int hand_suit;
    int field_card;
    int field_suit;
    hand_card = (int)(number[myhand->card_num[*input_data - 1]]) % SAME_CARD + 1;
    hand_suit = (int)(number[myhand->card_num[*input_data - 1]]) / SAME_CARD;
    field_card = (int)(number[field->card_num[field->hand_cnt-1]]) % SAME_CARD + 1;
    field_suit = (int)(number[field->card_num[field->hand_cnt-1]]) / SAME_CARD;

    answer = FALSE;
        if((hand_card == field_card)
        ||(hand_suit == field_suit))
        {
            answer = TRUE; 
        }

    return answer;
}

#if 0
/********************************/
/* リバースデッキ関数      */
/********************************/
void rebirth_deck(PLAYER_DATA *field, PLAYER_DATA *myhand)
{
    int rnd;//ランダム値格納用変数
    int tmp;
    int arr_cnt;//配列カウンタ
    srand((unsigned)time(NULL));

    for (arr_cnt=0; arr_cnt < CARD_MAX; arr_cnt--) {//デッキシャッフル
        if ((myhand->card_num[myhand->hand_cnt] == card_deck[rest_cnt]) ||
            (field->card_num[myhand->hand_cnt] == card_deck[rest_cnt])) {
            continue;
        }
        rnd = rand() % (CARD_MAX);
        tmp = card_deck[arr_cnt];
        card_deck[arr_cnt] = card_deck[rnd];
        card_deck[rnd] = tmp;

    }
    rest_cnt = myhand->hand_cnt+1;
}
#endif
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

+2

こんにちは。

最後に場に出したカードと手札以外のカードをシャッフルし再度山札を作る。

これって、場に出ているカードの内、最後以外を山札にしてシャッフルすれば良いと思います。
つまり、field_cardの最後の1枚手前までをcard_deckへ移動し、card_deckをシャッフルするわけです。

ところで、ざっと見た感じですが、delete_hand()本当に捨てちゃってますね。field_cardへ移動する必要はないですか? また、"(2)No"を選択した時に山札が無くなっていた時の処理も漏れてないですか?

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/02/12 03:18

    コメントが遅くなってしまい申し訳ありません。
    field_cardの一枚手前ということは下記コードのような関数で呼び出せばよいのでしょうか?手札の数値と重複するので私のコードにどこか不備があるかもしれませんが・・・
    また、No選択時の再シャッフル呼び出し処理がないのは私のミスでした。
    if (rest_cnt == CARD_MAX) {
    rebirth_deck(&field_card, &myhand);
    }

    中略

    void rebirth_deck(PLAYER_DATA *field, PLAYER_DATA *myhand)
    {
    int rnd;//ランダム値格納用変数
    int tmp;
    int arr_cnt;//配列カウンタ
    srand((unsigned)time(NULL));

    for (arr_cnt=0; arr_cnt < field->hand_cnt - 1; arr_cnt++) {//デッキシャッフル
    rnd = rand() % (field->hand_cnt - 1);
    tmp = card_deck[arr_cnt];
    card_deck[arr_cnt] = card_deck[rnd];
    card_deck[rnd] = tmp;

    }
    rest_cnt = (field->hand_cnt+myhand->hand_cnt)-1;
    }

    キャンセル

  • 2016/02/12 09:30

    考え方は間違っていないのですが、大きなポイントが1点漏れているようです。
    単なるミスと思っていたのですが、どうもそうではないようなので解説します。

    card_deck配列には手札として配ったカードのデータの残滓が残ってはいますが、手札から捨てられたものとその残滓は当然ですが一致しません。card_deckの残滓の一部は手札として残ったままですので。
    従って、rebirth_deckでcard_deckの残滓をシャッフルしても、それは手札として残っているものと重複しますし、また、重複した枚数だけ本来あるべきカードを含んでいません。

    リアルと同じようにカードをハンドリングしましょう。
    つまり、手札からカードを捨てたときはそれをfield_cardへ入れておき、rebirth_deck()の頭でその最後の一枚は残したままcard_deckへ移動後、card_deckをシャッフルしましょう。

    キャンセル

  • 2016/02/12 14:39

    Chironianさん、回答ありがとうございます。
    回答を踏まえて手札削除関数を下記のように変えたとき myhand->card_num[input_cnt] = myhand->card_num[input_cnt + 1];を消さないと重複してしまいますが、消してしまうと手札を表示したとき手札がずれなくなってしまいます。こういった場合どうしたらよいのでしょうか。(field_cardへの格納を追加しただけなのですが処理自体セメタリー関数とかぶっているかもしれません)何度も回答いただき大変申し訳ないのですが非常に困っております。再シャッフル関数ができればゲームはほぼ完成するのですが再シャッフル処理の考えに手間取っています。
    void delete_hand(PLAYER_DATA *myhand, PLAYER_DATA *field_card ,int *input_data)
    {
    unsigned int input_cnt;

    //入力された選択肢のカードを起点に手札を一枚ずつずらす
    for (input_cnt = *input_data - 1; input_cnt < myhand->hand_cnt - 1; input_cnt++) {
    myhand->card_num[input_cnt] = myhand->card_num[input_cnt + 1];
    field_card->card_num[field_card->hand_cnt] = myhand->card_num[*input_data - 1];
    }
    myhand->hand_cnt--;//手札を一枚減らす
    }

    キャンセル

0

手直しより1から書くほうが楽なので書いてみました。

struct CARD_ORDER{
    int card;
    int order;
};

int comp( const void* a, const void* b )
{
    struct CARD_ORDER* p = (struct CARD_ORDER*)a;
    struct CARD_ORDER* q = (struct CARD_ORDER*)b;
    return p->order - q->order;
}

void reshuffle( int last, const PLAYER_DATA* hand )
{
    struct CARD_ORDER list[CARD_MAX];
    size_t size = 0;
    int i, j, k;

    srand( (unsigned)time(NULL) );

    for( i = 0; i < CARD_MAX; i++ ){
        if( i == last ) continue;
        for( j = 0; j < hand->hand_cnt; j++ ){
            if( i == hand->card_num[j] ) break;
        }
        if( j < hand->hand_cnt ) continue;

        list[size].card = card;
        list[size].order = rand();
        size++;
    }
    qsort( list, size, comp );

    for( k = 0; k < size; k++ ){
        card_deck[k] = list[k].card;
    }
}

内容は一目瞭然だと思うので省略します。必要なら補足します。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.04%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる