dポイントプレゼントキャンペーン実施中!

 下記のようなプログラムで4人のコンピュータにババヌキをさせるプログラムを組んでる最中なんです。
 decklistまでは表示できてもshufflelistではコンパイルは通っても実行するとデバックが起きてしまって困っています。while文をなくすとちゃんと返り値を持って表示はできるんですがwhile分で繰り返したとたんデバックが起きるんですがその理由がわかりません。どうして無理でしょうか?ご教授願います。

decklistは整列されリストに保存した山札の関数。
shufflelistはランダムで保存していく関数。
personの関数はdecklistのほうで試したところコンパイルは通るんですがこちらもデバックが起きてしまいます。


#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<time.h>
#define N 53

//デッキのカードの構造体
struct card{
int t;
struct card *next;
}*deck;

//カードを配った後のそれぞれのプレイヤーの情報(仮
struct player{
int card;
struct player *nextcard;
struct player *nextturn;
};

//関数定義
struct card *talloc(void);
struct player *lalloc(void);
struct card *decklist(void);
struct card *shufflelist(void);
struct player *person(struct player *P);
void displist(void);

int main(void){
struct player *A,*B,*C,*D;
struct player *p;
p=person(A);
while(p!=NULL){
printf("%d ",p->card);
p=p->nextcard;
}
return 0;

}

//card構造体のセルの確保
struct card *talloc(void){
return (struct card *)malloc(sizeof(struct card));
}

//player構造体のセルの確保
struct player *lalloc(void){
return (struct player *)malloc(sizeof(struct player));
}

//カードを切る前の整列された山札の関数
struct card *decklist(void){
int i,add=1,count=0;
struct card *p;
deck=NULL;
for(i=0;i<53;i++){
if(count==4){
add+=1;
count=0;
}
p=talloc();
p->t=add;
p->next=p;
count++;
p->next=deck;
deck=p;
}
return deck;
}

//山札をシャッフルした後の山札の関数
struct card *shufflelist(void){
int i,a,count=0,r;
struct card *d,*p,*q,*shuffle;
p=decklist();
shuffle=NULL;

while(p!=NULL){
srand(time(NULL));
r=(int)rand()%(N-count);
for(i=0;i<r;i++){
p=p->next;
}
q=talloc();
q->t=p->t;
d=p->next;
p->next=d->next;
q->next=shuffle;
shuffle=q;
count++;
p->next=p;
}



return shuffle;
}

//プレイヤーへカードを配るための関数
struct player *person(struct player *PERSON){
struct player *a;
struct card *library;
int i;
PERSON=NULL;
library=decklist();
a=lalloc();
while(a!=NULL){
a->card=library->t;
for(i=0;i<4;i++){
library=library->next;
}
a->nextcard=a;
a=PERSON;
}

a->nextcard=NULL;
while(a!=NULL){
printf("%d ",a->card);

}

return PERSON;

}

A 回答 (6件)

とりあえず、カードを抜く処理について、抜いたカードの前後に対する


処理の一例として理解いただければ、いいかと思います。

が、実はシャッフルであれば、もっとお手軽にできます。
そう、カードの交換です。
------------------------------------------------------------
//山札をシャッフルした後の山札の関数
struct card *shufflelist3(void){
int i,j,cnt,r1,r2,t;
struct card *p,*p1,*p2;

srand(time(NULL));
cnt=rand()%53+30;

p=decklist();
for(j=0;j<cnt;j++){
p1=p2=p;
r1=(int)rand()%53;
r2=(int)rand()%53;
if(r1==r2) continue;
for(i=0;i<r1;i++) p1=p1->next;
for(i=0;i<r2;i++) p2=p2->next;

// r1番目とr2番目の入れ替え
t=p1->t;
p1->t=p2->t;
p2->t=t;
}
return p;
}
------------------------------------------------------------

で、気付いたかもしれませんが、ババ抜きにおいて山札は枚数が決まっていて、
カードが配布されたら0になるべきものであるので、配列でOKだったりします。
ポインタ構造は不定枚数の時に効果を発揮しますが、なにぶん、10枚目!といった際に
10枚目を探すことになりますので、例えば p[9]と一発で指定できる配列には
かないません。
私はプログラムは一回作ったら必ず見直すようにしています。
配列版にすると全体的な影響が大きすぎるので、プログラムは提示しませんが
上記よりものすごくシンプルになったりします。
#時間がない、といっている中、同じところばかり見ていてすみません。
#ただ、バグが発生しやすいCはプログラムの見直し、及び、確実な仕様の理解は必須です。


> 自分の頭がコンピュータ側としての理解の仕方に合わせれてないことを痛感しました。
> もうちょっと練り考えて見たいと思います。

そんなにコンピュータ側にあわせる必要もないです。
前に提示した私のサンプルはポインタのリスト構造が後ろに伸びていく、ただ
それをイメージしただけで、コンピュータ側にあわせたわけではないです。
カードを抜く処理が言葉だけでは伝わらなかったので、サンプルを作ろうと思ったのですが
ちょっと思うようにいかず、若干ですが、リスト構造作成の方に手を入れさせて頂きました。
私としてはカードを抜く処理が伝われば、それでいいです。

で、他処理ですが、

・プレーヤーにカードを配る処理
 →A,B,C,Dはグローバル変数にしてしまってもいいのではないでしょうか?
  その上で、これからもらうカードとしてa,b,c,d、及び、その手前をaa,bb,cc,ddとして
  山札から1枚引く関数を実行、カードが無くなったらループから脱出、って感じに
  すればいいでしょう。
  山札から1枚引く関数は前に提示した関数を参考にすればいいかと思います。

・ゲーム実行
 4人の内3人はコンピュータでしょうか?
 ババ抜きは本来は相手の表情等を伺っての戦略が必要ですが、流石にそういった処理は
 難しいので、何も戦略がないとすれば、単に乱数を使用しカードを抜いていく、といった
 処理をすればいいだけとなります。
 これも山札から1枚抜く処理が主立ったものになるかと思います。
 言葉で書くより面倒な処理は多いかとは思いますが、慣れればすぐにできるようになります。
 がんばってみてください。
    • good
    • 0
この回答へのお礼

助言ありがとうございます。
 確かに枚数が決まってる処理には不定なリストよりも配列のほうが直接ほしい値に接続できるのが利点でここでは走するべきでした。自分がリストを使おうと思ったのは最初の整列された山札からシャッフルの山札を作るときに配列だとランダムで配列を指定してそこのあたいをマタ別の配列に入れるときランダムで指定する配列の値がかぶってしまうのがきになりリストで実現するほうがイイと思ったのでリストにしたのがおっしゃるとおりむしろ難易度を上げてる気がしてなりませんね。。。。
 関数での使い方が凡庸性が高すぎて逆に混乱してしまうことが多々あり山札で一枚引かせる関数などその動作でどこまで細かく分けるのかまだよくわからないことが多いので確認していきたいと思います。
 ゲーム実行ではこのばばぬきプログラムはパソコンにババヌキの処理をやらせたいので作ろうとしました。設定はよくありがちな4人くらいが一般的かと思いそうしました。
 戦略というよりは個人的につけてみたい機能として「ジョーカーがあるとある動作をする」っという人間らしい考え方を組み込みたいと思ってますが時間がないのでそこまで手が出せるかわからないのが現状です。なので今は短的にプログラムの完成を目指して生きたいと思ってます。
 リストでの実現はこれで勉強になるのですが、ただ起源もあり、配列の場合のプログラムが探したところあったのでとりあえずこちらを優先的にプログラムを完成させ、リストでのプログラムも後々完成させたいと思っています。そちらの法に力を入れたいのでこの質問版はとりあえず〆させていただきます。
 勝手ながらプログラムそのものを変えてしまって7o8さんにはご迷惑かけました。すみません。
ただ今後に生かせるようにがんばっていきます!!ありがとうございました。お世話になりました。

お礼日時:2009/10/20 04:34

まずはわかりやすいように作るべき何ですが、ここがプログラムの


難しいところであって、各個人に随分と考え方が異なってきてしまいます。

で、申し訳ないんですが、現在のカードのリスト構造における作成方法が
私とあっていないので、どうあるべきか?というのが少々難しく感じてしまっています。

私のイメージするカードのリスト構造は上から順々に生成していく、というもので、
頭の中の整理がつけばどうでもいいことなんですが、イマイチ後ろのカードから
作成していく感覚になじめていないのです。

とりあえず、私の方で作成した、簡単に動作確認をした decklist2及び
shufflelist2を以下に記します。
ご参考になれば幸いです。
#変数の命名関連は参考にしないでください。(^_^;;;;;
#decklistは正常動作しているのを確認しましたが、こういう作成例があるということで。


struct card *decklist2(void){
int i,add=1,count=0;
struct card *p,*pp,*deck;

pp=p=talloc();
for(i=0;i<53;i++){
if(count==4){
add+=1;
count=0;
}
deck=p;
p->t=add;
p->next=talloc();
p=p->next;
count++;
}
free(deck->next);
deck->next=NULL;

return pp;
}


//山札をシャッフルした後の山札の関数
struct card *shufflelist2(void){
int i,j,count=0,r;
struct card *pd,*pp,*p,*q,*qd,*shuffle;

srand(time(NULL));

pp=p=decklist();
qd=q=shuffle=talloc();
for(j=0;j<53;j++){
r=(int)rand()%(53-count);
for(i=0;i<r;i++) {
pd=p;
p=p->next;
}
//シャッフル後のカードに対する処理
q->t=p->t;
qd=q;
q->next=talloc();
q=q->next;

// pのリストからカードを抜く
if(r==0)pp=p->next;//先頭のカードを抜く場合はppを次のカードに移す。
else pd->next=p->next;//そうでなければ前後のカードからの関係を外す。
free(p);//抜かれたカードの領域解放

count++;
pd=p=pp;
}
// 最後の1枚に対する処理
free(qd->next);
qd->next=NULL;

return shuffle;

}

この回答への補足

プログラムまでマコトにありがとうございます。なかなか理解するには自分のスペックがちょっと足らない気がします。。。@期限がまもないので理解していきたい気持ち半分プログラムを完成させなければという気持ち半分です。
自分がおもっていたよりも多くの変数が用意なされてたことにちょっと驚きました。また自分の頭がコンピュータ側としての理解の仕方に合わせれてないことを痛感しました。
もうちょっと練り考えて見たいと思います。

補足日時:2009/10/19 02:03
    • good
    • 0

事故レス(^_^



> ただ単純に元々のpの先頭にリセットしたかったのであれば、単純に
> 「pp=p=decklist();」に変更、while文最後の「p->next=p;」を
> 「p=pp;」にすればいいだけですよ。

分かって頂けるとは思いますが、上記変更ではwhile文は終了しません。
単純にfor文に置き換えるのが手でしょうね
    • good
    • 0

もう一回プログラムを見直してみました。


ご確認頂けますか?


・shufflelist関数

> for文で繰り返した後での位置からまたカウントすると思ったので一度リセット
> する意味で返したつもりなんですけどどういう理由からNGなのでしょうか?

 ただ単純に元々のpの先頭にリセットしたかったのであれば、単純に
 「pp=p=decklist();」に変更、while文最後の「p->next=p;」を
 「p=pp;」にすればいいだけですよ。

 ただ、pからカードを抜く処理がうまくいっていないように見えるのが
 気になります。
 カードを抜くには一つ前のpに対するp->nextに対し、抜かれたpのp->nextを
 設定する必要があります。
 while文内のforでpツリーを追う際に一つ前のpを保持している必要がありますよね?

 あと、シャッフルするのに乱数を使うのは当然ですが、乱数初期化関数を
 ループ内で何度も実行するものではありません。
 また、乱数初期化関数に定数を設定するものでもないです。
 #乱数のハズなのに同じ数しか出てこないことになりますよ。


・person関数
 恐らくですが、libraryへの値設定そのものまではうまくいっています。
 で、while文の中でa=PERSON;を実行しているのは何故でしょうか?
 結局これが原因で1回でroopを抜け、その後のa->nextcard=NULL;で
 aがNULLなのに実行してしまい、エラーになっています。

 で、エラーとは全然関係ないのですが、person関数はmainからユーザAに
 対して実行するようになっています。
 ユーザAのためにカードを初期化する、って変じゃないですか?
 (これはデバッグ用?shufflelist関数内にあるのはOKだとは思います)
 そもそも関数仕様からすると、main()関数内でAに対し領域を割り当てる
 べきかと思います。


・main関数
 まずはA、B、C、Dに領域を割り当てましょう。
 で、最初に呼ぶのはshufflelist関数ですよね?
 person関数でユーザにカードを割り当てるのであれば、1枚1枚割り当てるような
 関数に変更した方がいいかと思います。
 (カードは53枚ありますからね)

・その他
 カードを抜く関数を作成した方がいいですかね。
 そーすればshufflelist関数でも使用できますし、ユーザからのカードを
 抜く処理にも使えます。
 カードリストから先頭の削除を行う処理がキモですが、ポインタのポインタを
 使えばいいかとおもいます。

この回答への補足

助言ありがとうございます。
>ただ単純に元々のpの先頭にリセットしたかったのであれば、単純に
>pp=p=decklist();」に変更、while文最後の「p->next=p;」を
>p=pp;」にすればいいだけですよ。
 この部分ですがp->nextを最初に戻すのはリストの先頭であるshuffleをさすべきだと思ったのでp->next=shuffle;だと思いました。

 person関数では処理がおかしいですね。ありがとうございます。
乱数のところですがそういう仕様だとは知らずwhile分の中に入れてました。

 mainからその他については個人的にperson関数では配るだけをさせメインの中で配られたそれぞれのリストをさすためのA,B,C,Dを用意させそこに代入させようかと思います。カードを抜く関数は同時に捨てさせる機能を搭載してプレイヤー間で使いたいとおもっています。

チョット自分でも7o8さんのおっしゃっている
>ただ、pからカードを抜く処理がうまくいっていないように見えるのが>気になります。カードを抜くには一つ前のpに対するp->nextに対し、
>抜かれたpのp->nextを 設定する必要があります。while文内のforでp
>ツリーを追う際に一つ前のpを保持している必要がありますよね?
の部分は新しく*dをつくりd=p->next;p->next=d->next;free(d);と続けそのpのさしている部分を開放しなくせばいいと思い組んでみましたが、1回目は返せても2回目以降の試行でもでてきます。
今のところの改良し手を加えたプログラムは下記です。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<time.h>
#define N 53

//デッキのカードの構造体
struct card{
int t;
struct card *next;
};

//カードを配った後のそれぞれのプレイヤーの情報(仮
struct player{
int card;
struct player *nextcard;
struct player *nextturn;
};

//関数定義
struct card *talloc(void);
struct player *lalloc(void);
struct card *decklist(void);
struct card *shufflelist(void);
struct player *person(void);
void displist(void);
//void displist(struct player *hito);

int main(void){
struct player *A,*B,*C,*D;
/*A=person();
displist(A);
B=person();
displist(B);
C=person();
displist(C);
D=person();
displist(D);
*/
displist();
return 0;

}

//card構造体のセルの確保
struct card *talloc(void){
return (struct card *)malloc(sizeof(struct card));
}

//player構造体のセルの確保
struct player *lalloc(void){
return (struct player *)malloc(sizeof(struct player));
}

//カードを切る前の整列された山札の関数
struct card *decklist(void){
int i,add=1,count=0;
struct card *p,*deck;
deck=NULL;
for(i=0;i<53;i++){
if(count==4){
add+=1;
count=0;
}
p=talloc();
p->t=add;
p->next=p;
count++;
p->next=deck;
deck=p;
}
return deck;
}

//山札をシャッフルした後の山札の関数
struct card *shufflelist(void){
int i,j,a,count=0,r;
struct card *d,*p,*q,*shuffle;
p=decklist();
shuffle=NULL;
srand(time(NULL));

//for(j=0;j<20;j++){
r=(int)rand()%(51-count);
for(i=0;i<r;i++){
p=p->next;
}
q=talloc();
q->t=p->t;
d=p->next;
p->next=d->next;
free(d);
q->next=shuffle;
shuffle=q;
count++;
p->next=shuffle;


//}



return shuffle;

}


//プレイヤーへカードを配るための関数
struct player *person(void){
struct player *a,*hito;
struct card *library,*old;
int i;
hito=NULL;
library=decklist();
old=decklist();

while(library->next!=NULL){
a=lalloc();
a->card=library->t;
old->next=library->next;
for(i=0;i<4;i++){
library=library->next;
}
a->nextcard=hito;
hito=a;
}



return hito;

}


//表示用関数
void displist(void){
struct card *p;
p=shufflelist();
while(p!=NULL){
printf("%d ",p->t);
p=p->next;
}
}

補足日時:2009/10/18 03:16
    • good
    • 0

shufflelist()内のwhile文継続条件は pがNULLでないこと、となっています。


で、これはどこかでpがNULLになることを期待しているとは思うのですが、
そのwhile文最後で、「p->next=p;」を実行しています。
pがNULLになることがあるのであれば、この実行は NGですし、おそらく
それが原因で異常終了しているかと思います。

person()も同様で while文継続条件は aがNULLでないこと、となっています。
つまり、while文を抜けた時点で aはNULLであり、その直後に
「a->nextcard=a;」を実行しています。

確認してみては如何でしょうか?

この回答への補足

助言ありがとうございます。

最後にあるp->next=p;は自分でも確信はないんですが上のfor文でポインタを移動し値を示しているのでwhile文で次の処理をするときp->nextがshuffleからでなくfor文で繰り返した後での位置からまたカウントすると思ったので一度リセットする意味で返したつもりなんですけどどういう理由からNGなのでしょうか?今書いて思ったのでリセットという意味では一度p->nextにshuffleをいれるとリセットされると思ったんですがこれも何かでます。。。

person()の方は条件文を間違えました。このときはlibraryからそれぞれプレイヤーに配るので判定はaではなくlibraryの方でやってみましたがこれでもだめでした。
まだCプログラムを書く上で知識が足りない部分もあり完成させるのに時間がかかるかもしれませんがこれを完成させたいと思っています。
期限も間近に近づいていることもあり焦っています。。。
さまざまな意見よろしくお願いします。

補足日時:2009/10/17 00:25
    • good
    • 0

まず、「デバッグが起きる」という言い方はしません。


添付の画像のようなメッセージボックスが出ていると思いますが、これを見ると「例外が発生する」という記述があります。
これであれば通じます。

いまひとつ何がやりたいのか分かりませんが、まず「例外が発生する」原因について

person関数で変数[PERSON]にNULLを設定しています。
その後、[PERSON]はperson関数内で変更されていません。
whileの中で変数[a]に変数[PERSON]の値を入れています。
(結果、変数[A]はNULLになります。)

その後、[a->nextcard=NULL]の行で変数[a]がNULLのため「例外が発生」しています。

C言語の変数名は小文字から始まり、なるべく小文字で表現するのが通例です。

最初[PERSON]を見たとき、defineで定義されている定数かと思ってしまいました。。。
「ばばぬきプログラムについて」の回答画像1
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!