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

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

ただいまの
回答率

89.09%

ビット演算を使った配列の初期化

解決済

回答 3

投稿

  • 評価
  • クリップ 2
  • VIEW 2,115

こんにちは
現在52枚のカードを使ったゲームを作成しています。
Sはスペード、Hはハート、Dはダイヤ、Cはクラブというようにアルファベット一文字を絵柄に見立てています。
下記のコードはまだ作成途中のコードなのですがカードの数値の初期化処理が冗長になってしまいROMとRAM上に大きなサイズを取ってしまいます。
*number[]配列宣言時に初期化を行ってしまっていることが原因なのですが、ビット演算を使用して初期化できればデータサイズは小さくできると思ったのですが
うまく格納する方法が浮かびません。

例えばinit_card関数内で*numberを初期化するとしてどのようにビット演算を使ってカードの数字を格納したらよいか思い浮かびません。

カードをどのように考えればビット演算を使用してうまく初期化できるかご教授願えないでしょうか。できればビット演算を使って下記コードと同じ処理が問題なくできるようにしたいです。
よろしくお願いいたします。

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

#define CARD_MAX (52)//カード枚数
#define SAME_SUIT (13)//同じ柄のカード枚数
#define INIT_HAND (5)//初手

/*構造体定義*/
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[] = { "1S", "2S", "3S", "4S", "5S", "6S", "7S", "8S", "9S", "10S", "11S", "12S", "13S",//スペード
                   "1C", "2C", "3C", "4C", "5C", "6C", "7C", "8C", "9C", "10C", "11C", "12C", "13C",//クラブ
                   "1D", "2D", "3D", "4D", "5D", "6D", "7D", "8D", "9D", "10D", "11D", "12D", "13D",//ダイヤ
                   "1H", "2H", "3H", "4H", "5H", "6H", "7H", "8H", "9H", "10H", "11H", "12H", "13H", };//ハート


/*関数プロトタイプ宣言*/
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 main(int argc, char*argv[])
{
    PLAYER_DATA myhand;//プレイヤーの手札構造体
    PLAYER_DATA field_card;//場のカード構造体

    int draw_cnt;//ドローカウンタ
    int input;//プレイヤー入力

    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("捨てるカード番号を入力してください:");
        scanf("%d", &input);
        cemetery_card(&field_card, &myhand,&input);//card_dataの配列は[0~4]出力のため関数内で-1する
        delete_hand(&myhand, &input);
        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);
        printf("\n山札(残数) %d 枚\n", CARD_MAX - rest_cnt);
        break;
    }

    printf("\nゲームクリアおめでとうございます。\n");
    printf("クリアするのに%dターンかかりました。\n", turn_cnt);
    printf("このまま終了します。\n");

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

    int rnd;//ランダム値格納用変数
    int tmp;
    srand((unsigned)time(NULL));

    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;
    select = 0;

    //ドローしたカード枚数分画面に表示
    for (myhnd_cnt = 0; myhnd_cnt < myhand->hand_cnt; myhnd_cnt++) {
        select++;
        printf("(%d)%s", select, number[myhand->card_num[myhnd_cnt]]);

    }

    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--;//手札を一枚減らす
}
  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 3

checkベストアンサー

+1

ビット演算を使って

初期化に使うというのがよくわからないので外しているかもしれませんが、こういった感じのことでしょうか?
復号に余計な処理が入りますが、ポインタが無くなってカード情報一枚が2~3バイトから1バイトになるので(コンパイラのchar型配列初期化の扱いにもよりますが)トータルでは消費メモリは減らせていると思うのですが・・・

char number[] = {'\x01','\x02','\x03','\x04','\x05','\x06','\x07','\x08','\x09','\x0A','\x0B','\x0C','\x0D','\x0E', //スペード
                 '\x11','\x12','\x13','\x14','\x15','\x16','\x17','\x18','\x19','\x1A','\x1B','\x1C','\x1D','\x1E', //クラブ
                 '\x21','\x22','\x23','\x24','\x25','\x26','\x27','\x28','\x29','\x2A','\x2B','\x2C','\x2D','\x2E', //ダイヤ
                 '\x31','\x32','\x33','\x34','\x35','\x36','\x37','\x38','\x39','\x3A','\x3B','\x3C','\x3D','\x3E', } ;//ハート
char suit[] = { 'S', 'C', 'D', 'H' };
#define get_suit_char(n) suit[n>>4]
#define get_number(n) (n&0x0F)
/* 中略 */
        printf("(%d)%c%d", select, get_suit_char(number[myhand->card_num[myhnd_cnt]]),get_number(number[myhand->card_num[myhnd_cnt]]) );

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2016/02/07 20:10

    kozuchiさん、回答ありがとうございます。
    申し訳ありません。私の質問の書き方がわかりにくかったです。
    numberのメモリを節約しようと思っていましたが、kozuchiさんの認識で合っています。
    数値と絵柄を分ければビット演算で出力させることが可能だということがわかりました。
    Chirionianさんの回答と組み合わせてコーディングを進めていこうと思います。
    本当にありがとうございました。

    キャンセル

+1

こんにちは。

number[]を使っているのは、printf("(%d)%s", select, number[myhand->card_num[myhnd_cnt]]);だけですね? この表示のためのnumber[]の初期化がちょっと勿体無く感じていると言うことですね。

現在の姿も「有り」とは思いますが、少しでもメモリを節約するため、numberの添字から上記printf()の文字を計算したいと理解しました。

下記イメージでどうでしょうか?

size_t index=myhand->card_num[myhnd_cnt];
static char const card_type[]={'S', 'C', 'D', 'H'};
printf("(%d)%d%c", select, index/13+1, card_type[index/13];


とと、すいません。Cでsize_tが使えましたっけ? だめならunsignedにして下さい。
コンパイル確認してないのでエラーがでたら適宜修正下さい。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2016/02/07 19:32

    >Cでsize_tが使えましたっけ?
    OKですよd^^

    キャンセル

  • 2016/02/07 20:00

    Chironianさん、いつも回答ありがとうございます。
    申し訳ありません。私の質問の書き方がわかりにくかったかもしれませんね。
    ご回答通り一か所しか使っていないnumberのメモリを節約しようと思っていました。
    ベストアンサーをどちらの方につけるか迷ったのですが、ビット演算を使用したkozuchiさんにつけさせていただきました。
    カードを考えるとき数字と絵柄を分けて、枚数52枚のため13で割り、数値は割った余りを出力すればうまく処理できました。私のカードの考え方だとまずこういった発想は出てきませんでした。トータルで考えた場合、お二人の回答のどちらのほうがメモリ節約にいなるかまだ調べていないのですが、より消費量の少ないほうを使って完成させていこうと思います。
    本当ににありがとうございました。

    キャンセル

  • 2016/02/07 21:19

    cateyeさん、フォローありがとうございます。
    n6n9Qsmt8gLjwKwさん、頑張ってください。

    キャンセル

+1

number は、 0..51 の数字を "1S", ... "13H" のカード名に変換する為につかっていますね。
変換表をカードの数だけ用意する必要はありません。
つぎのようにして、数字 <--> カード名の変換ができます。
ただし以下では、変換がしやすいように カード名は CA, C2, ... CJ, CQ, CK のように
スーツ + 数字 の順とし、さらに数字は A, 2, 3, ,, J, Q, K としています。

#include <stdio.h>
#include <stdlib.h>

#define MAX_CARD_NUM (13)
#define MAX_SUIT (4)
#define MAX_CARD_COUNT (MAX_CARD_NUM * MAX_SUIT)

char * SUITS[] = {"C", "D", "H", "S"};
char * NUMS[]  = {"A", "2", "3", "4", "5", "6", "7", "8", "9",
                  "10", "J", "Q", "K"};

void int2card(unsigned char c, char * str) {
  *str ='\0';
  int num = c & 0xFF;
  int s = num / MAX_CARD_NUM;
  int n = num % MAX_CARD_NUM;
  sprintf(str, "%s%s", SUITS[s], NUMS[n]);
}

unsigned int card2int(char* card) {
  int s = 0;
  int n = 0;

  char s0 = card[0];
  for (int i = 0; i < MAX_SUIT; i++) {
    if (card[0] == SUITS[i][0]) {
      s = i;
      break;
    }
  }
  for (int i = 0; i < MAX_CARD_NUM; i++) {
    if (card[1] == NUMS[i][0]) {
      n = i;
      break;
    }
  }
  return (unsigned int)(s * MAX_CARD_NUM + n);
}

int main(int argc, char*argv[]) {
  char str[4];
  for (unsigned char c = 0; c < MAX_CARD_COUNT; c++) {
    int2card(c, str);
      printf("%2d: %s ", c, str);
      if ((c + 1) % MAX_CARD_NUM == 0) {
        printf("\n");
      }
  }
  printf("\n");
  for (unsigned char c = 0; c < MAX_CARD_COUNT; c++) {
    int2card(c, str);
      unsigned char num = (unsigned char)card2int(str);
    printf("%2s: %2d ", str, (int)num);
      if ((c + 1) % MAX_CARD_NUM == 0) {
        printf("\n");
      }
  }
}


実行例

0: CA  1: C2  2: C3  3: C4  4: C5  5: C6  6: C7  7: C8  8: C9  9: C10 10: CJ 11: CQ 12: CK 
13: DA 14: D2 15: D3 16: D4 17: D5 18: D6 19: D7 20: D8 21: D9 22: D10 23: DJ 24: DQ 25: DK 
26: HA 27: H2 28: H3 29: H4 30: H5 31: H6 32: H7 33: H8 34: H9 35: H10 36: HJ 37: HQ 38: HK 
39: SA 40: S2 41: S3 42: S4 43: S5 44: S6 45: S7 46: S8 47: S9 48: S10 49: SJ 50: SQ 51: SK 

CA:  0 C2:  1 C3:  2 C4:  3 C5:  4 C6:  5 C7:  6 C8:  7 C9:  8 C10:  9 CJ: 10 CQ: 11 CK: 12 
DA: 13 D2: 14 D3: 15 D4: 16 D5: 17 D6: 18 D7: 19 D8: 20 D9: 21 D10: 22 DJ: 23 DQ: 24 DK: 25 
HA: 26 H2: 27 H3: 28 H4: 29 H5: 30 H6: 31 H7: 32 H8: 33 H9: 34 H10: 35 HJ: 36 HQ: 37 HK: 38 
SA: 39 S2: 40 S3: 41 S4: 42 S5: 43 S6: 44 S7: 45 S8: 46 S9: 47 S10: 48 SJ: 49 SQ: 50 SK: 51

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

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

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