カードをシャッフルするプログラム

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
羽流布

カードをシャッフルするプログラム

#1

投稿記事 by 羽流布 » 18年前

またまた質問に来ました<うるふ>です。

私は小説を書いているのですが、とある本でカードを利用した物語作成術が載っておりました。そこで、これをCで実現しようと思い、取り組んでいるのですが行き詰まりました……。

乱数を発生させる方法は分かったのですが、以下のプログラムでは結果が重複してしまって、同じものを二回引くという矛盾が生じてしまいます。

この矛盾を無くしたいのですが、何か良い案はありませんでしょうか?
/*************************
 物語作成お助けプログラム
*************************/

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

#define NUM 24

// 関数のプロトタイプ
int GetRandom(int min, int max);

int main(void) {
	char rune[NUM][128] = { {"知恵"}, {"生命"}, {"信頼"}, {"勇気"},
                                 {"慈愛"}, {"秩序"}, {"至誠"}, {"創造"},
                                 {"厳格"}, {"治癒"}, {"理性"}, {"節度"},
                                 {"調和"}, {"結合"}, {"庇護"}, {"清楚"},
                                 {"善良"}, {"解放"}, {"変化"}, {"幸運"},
                                 {"意思"}, {"誓約"}, {"寛容"}, {"公式"} };
	int i, n;
	
	srand( (unsigned int)time(NULL) );
	
	for (i = 0; i < 6; i++) {
		n = GetRandom(0, NUM - 1);
		switch (i) {
			case 0: printf(" 主人公の現在―――〉"); break;
			case 1: printf(" 主人公の近い未来―〉"); break;
			case 2: printf(" 主人公の過去―――〉"); break;
			case 3: printf(" 援助者――――――〉"); break;
			case 4: printf(" 敵対者――――――〉"); break;
			case 5: printf(" 結末(目的)―――〉"); break;
		}
		printf("%s", rune[n]);
		if ( GetRandom(0, 1) ) {
			printf("<逆>");
		}
		printf("\n");
	}
	return (0);
}

// 範囲乱数を実現する関数
int GetRandom(int min, int max) {
	return ( min + (int)(rand() * (max-min+1.0) / (1.0 + RAND_MAX)) );
}

フリオ

Re:カードをシャッフルするプログラム

#2

投稿記事 by フリオ » 18年前

 
 カードをシャッフルするプログラムです。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void)
{
	char *rune[/url] = {"知恵", "生命", "信頼", "勇気", "慈愛", "秩序", "至誠", "創造",
			"厳格", "治癒", "理性", "節度", "調和", "結合", "庇護", "清楚",
			"善良", "解放", "変化", "幸運", "意思", "誓約", "寛容", "公式",};
	char *temp;
	int i, j;
	
	srand((unsigned)time(NULL));
	for(i = 0; i < 24; i ++){
		printf("%s ", rune);
		if(i % 8 == 7) putchar('\n');
	}
	i = 24;
	while(i > 1){
		j = rand() % i --;
		temp = rune;
		rune = rune[j];
		rune[j] = temp;
	}
	putchar('\n');
	for(i = 0; i < 24; i ++){
		printf("%s ", rune);
		if(i % 8 == 7) putchar('\n');
	}
	return 0;
}

 

管理人

Re:カードをシャッフルするプログラム

#3

投稿記事 by 管理人 » 18年前

シャッフルについてはフリオさんが紹介なさっていますので、
同じものを2回引かない方法について。

方法は色々あると思います。
配列にデータを入れておき、選択した項目をどかして、左に詰めていく方法。
絵に描くとこんな感じ。
[0]~[6]のそれぞれの配列要素に2桁の乱数が入っているとします。

[0] [1] [2] [3] [4] [5] [6] 
23  45  62  12  73  21  23  

こんな風に値が入っているとします。
乱数で0~6の値を出し、仮に4が出たとします。

[0] [1] [2] [3] [4] [5] [6] 
23  45  62  12  73  21  23  

この値を取り出したいわけですね。
[4]の値を取り出し、それ以降の数値を左に寄せましょう。

[0] [1] [2] [3] [4] [5] [6] 
23  45  62  12  21  23  

次は乱数で0~5までの値を出します。仮に2が出たとします。[2]を取り出し、左へつめる・・。

これを繰り返せば同じ値を拾う事はありません。


これはもし文字列ならポインタを使うことになるでしょうから、
もし他の方法がよければもう一つ紹介します。

もう一つは構造体を使った方法です。

typedef struct{
    int flag;
    char st[64];
}ram_t;

ramt_t ram[10];

こんな風に構造体の配列を用意し、最初構造体の中のflagは全て0にしておきます。
で、stに文字列をいれます。
乱数でi=0~9の値を出し、ram.flagが0ならその中のstの文字列を使います。
使ったらram.flag=1;にしておきます。
次またこの配列要素を調査した時、flagは1になっていますから、既に使ったという事がわかりますし、
そうなったら他の要素を利用しましょう。


等など、色々方法はあると思います。


どうでしょう?おわかりいただけましたでしょうか。

羽流布

Re:カードをシャッフルするプログラム

#4

投稿記事 by 羽流布 » 18年前

>フリオさん
 ありがとうございます^^
 変数宣言の中括弧は不必要だったのですね。
 そこら辺も曖昧でしたので助かりました。

>管理人さん
 ポインタはまだ習っていませんが、構造体は今週学んだばかりです。
 構造体って、こんな使い方が出来るんですね。全く考えていませんでした……。
 というわけで、構造体のほうでプログラムを作り変えてみます。

羽流布

Re:カードをシャッフルするプログラム

#5

投稿記事 by 羽流布 » 18年前

>フリオさん
 j = rand() % i --; の意味が理解できないのですが……?

羽流布

Re:カードをシャッフルするプログラム

#6

投稿記事 by 羽流布 » 18年前

下記のプログラムを実行したら、

 32: 左辺値が必要(関数 main )

というエラーが出たのですが、何が原因でしょうか?
/*************************
 物語作成お助けプログラム
*************************/

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

#define NUM 24

typedef struct {
	int flag;
	char st[64];
} ram_t;

// 関数のプロトタイプ
int GetRandom(int min, int max);

int main(void) {
	char rune[NUM][64] = { "知恵", "生命", "信頼", "勇気",
                                "慈愛", "秩序", "至誠", "創造",
                                "厳格", "治癒", "理性", "節度",
                                "調和", "結合", "庇護", "清楚",
                                "善良", "解放", "変化", "幸運",
                                "意思", "誓約", "寛容", "公式" };
	ram_t ram[NUM];
	int i, n;
	
	srand( (unsigned int)time(NULL) );
	
	for (i = 0; i < NUM; i++) {
		ram.st = rune; // ここが32行目です
		ram.flag = 0;
	}
	for (i = 0; i < 6; i++) {
		switch (i) {
			case 0: printf(" 主人公の現在―――〉"); break;
			case 1: printf(" 主人公の近い未来―〉"); break;
			case 2: printf(" 主人公の過去―――〉"); break;
			case 3: printf(" 援助者――――――〉"); break;
			case 4: printf(" 敵対者――――――〉"); break;
			case 5: printf(" 結末(目的)―――〉"); break;
		}
		n = GetRandom(0, NUM - 1);
		while (ram[n].flag == 1) {
			n = GetRandom(0, NUM - 1);
		}
		printf("%s", ram[n].st);
		ram[n].flag = 1;
		if ( GetRandom(0, 1) ) {
			printf("<逆>");
		}
		printf("\n");
	}
	return (0);
}

// 範囲乱数を実現する関数
int GetRandom(int min, int max) {
	return ( min + (int)(rand() * (max-min+1.0) / (1.0 + RAND_MAX)) );
}

Hermit

Re:カードをシャッフルするプログラム

#7

投稿記事 by Hermit » 18年前

問題の行は、
strcpy(ram.st,rune);
としておけば良いようです。

又は、構造体を
typedef struct {
int flag;
char *st;
} ram_t;
にしてあれば、そのまま
ram.st=rune;
でもいいでしょう。

box

Re:カードをシャッフルするプログラム

#8

投稿記事 by box » 18年前

>  32: 左辺値が必要(関数 main )

strcpy() を使ってください。

羽流布

Re:カードをシャッフルするプログラム

#9

投稿記事 by 羽流布 » 18年前

皆さん、ありがとうございますm(_ _)m

どうやら、ポインタが絡んでくるようですね。
私はまだ習っていないので、よく分かりませんが……。

とりあえず、これで目的のプログラムは完成しました。
欲を言えば、<逆>を表示する確率を変更できればいいのですが、無理なんでしょうね↓↓

Hermit

Re:カードをシャッフルするプログラム

#10

投稿記事 by Hermit » 18年前

普通に、rand() 使えばいいだけではないでしょうか。
if ((rand() % 10) > 6) printf("<逆>");
例えばこれなら 30% 程度かな。

if (rand() < (RAND_MAX /4)) printf("<逆>");
だったら、25 % くらい

羽流布

Re:カードをシャッフルするプログラム

#11

投稿記事 by 羽流布 » 18年前

>Hermitさん
 こうすれば確率を調整できるのですか。
 ありがとうございますm(_ _)m

 でも、実行してみると全部<逆>になったりと……なんか前より極端になった気が……。
 まぁ、0か1かという乱数ですから、確率なんてあんまり意味ない気がしてきました○TZ

管理人

Re:カードをシャッフルするプログラム

#12

投稿記事 by 管理人 » 18年前

先日から
「ポインタ」=「やめる、避ける」
と嫌ってらっしゃるようですが、
char *st="shuffle";
という記述はなんら難しいものではなく、
char st[10]="shuffle";
などと宣言したのと同じように扱えます。
まだ学校で習って無いようでしたら難しくないですからネットに沢山ある参考になるサイトをのぞいてみてはいかがでしょうか?
今の範囲の中では実行する上で特に詳しい仕組みを理解していなくても使えますよ。

あと、
>j = rand() % i --; の意味が理解できないのですが……?
についてですが、
i--;

i++;
は式が評価された後でマイナス、プラスされます。一方その逆で、
--i;
++i;
という書き方もあり、これは式を評価する前に増減させます。
つまり、式と、インクリメント、デクリメントの式をひとまとまりで書いただけであり、
j = rand() % i;
i--;
と書いたのと同じです。
for(i=0;i<10;i++)
などと書きますよね。
i++;
は式が評価された後に、計算されますから、

i=1;
printf("%d\n",i++);
printf("%d\n",i);
これを実行すると、
1
2
となります。上記プログラムは同様に
i=1;
printf("%d\n",i);
i++;
printf("%d\n",i);
と同じことです。

羽流布

Re:カードをシャッフルするプログラム

#13

投稿記事 by 羽流布 » 18年前

>管理人さん
 そうですよね、未知なる物を嫌ってはいけませんよね。
 好奇心を持って取り組みたいと思います^^

 i++ とかの ++ の部分は今まであまり意識せずに使っていました。
 なので、参考になりました。
 ありがとうございますm(_ _)m

羽流布

Re:カードをシャッフルするプログラム

#14

投稿記事 by 羽流布 » 18年前

 皆さんの意見を参考にプログラムを改良してみました^^

 でも、実際にツールとして使うためには、プログラムの終了条件に「エンターを押して終了」などの処理が必要だということに気付きました。
 そこで、エンターの文字列表現が知りたいのですが、改行の表現では上手くいかないようです……。どのように表現すればいいのでしょうか?
/*************************
 物語作成お助けプログラム
*************************/

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

#define NUM 24

typedef struct {
	int flag;
	char *st;
} ram_t;

int main(void) {
	char *rune[NUM] = { "知恵", "生命", "信頼", "勇気",
                             "慈愛", "秩序", "至誠", "創造",
                             "厳格", "治癒", "理性", "節度",
                             "調和", "結合", "庇護", "清楚",
                             "善良", "解放", "変化", "幸運",
                             "意思", "誓約", "寛容", "公式" };
	char ch;
	ram_t ram[NUM];
	int i, j, n;
	
	srand( (unsigned int)time(NULL) );
	
	for (i = 0; i < NUM; i++) {
		ram.st = rune;
		ram.flag = 0;
	}
	for (i = 0; i < 6; i++) {
		switch (i) {
			case 0: printf(" 主人公の現在―――〉"); break;
			case 1: printf(" 主人公の近い未来―〉"); break;
			case 2: printf(" 主人公の過去―――〉"); break;
			case 3: printf(" 援助者――――――〉"); break;
			case 4: printf(" 敵対者――――――〉"); break;
			case 5: printf(" 結末(目的)―――〉"); break;
		}
		j = NUM;
		n = rand() % j--;
		while (ram[n].flag == 1) {
			n = rand() % j--;
		}
		printf("%s", ram[n].st);
		ram[n].flag = 1;
		if ( (rand() % 10) < 3 ) {
			printf("<逆>");
		}
		printf("\n");
	}
	printf("エンターで終了\n");
	if ( scanf("%c", &ch) ) {
		return (0);
	}
}

バグ

Re:カードをシャッフルするプログラム

#15

投稿記事 by バグ » 18年前

環境によって違う場合もありますので、下記のようなプログラムで調べてみてはいかがでしょうか?
押したキーのコードを16進数で表示します。

#include	<stdio.h>
#include	<conio.h>

int main(void)
{
	unsigned char chKey;

	while( 1 )
	{
		chKey = getch();

			if (chKey != 0x00)
			{
				printf("%X\n", chKey);
			}
	}

	return 0;
}

羽流布

Re:カードをシャッフルするプログラム

#16

投稿記事 by 羽流布 » 18年前

一応、Dと出ましたけど、これって環境によって違うということですか?
それでは、実用的ではない気がするのですが……?
どうしましょ○TZ

フリオ

Re:カードをシャッフルするプログラム

#17

投稿記事 by フリオ » 18年前

 
 こういうことでしょうか。
Win2000、BCC32 5.5.1で動作確認。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <conio.h>

#define RUNE 24
#define ITEM 6

void Shuffle(char **card, int number)
{
	char *temp;
	int i, j;
	
	i = number;
	while(i > 1){
		j = rand() % i --;
		temp = card;
		card = card[j];
		card[j] = temp;
	}
	return;
}

void Print(char **card)
{
	char *item[/url] = {"  主人公の現在  ", "主人公の近い未来", "  主人公の過去  ",
			   "     援助者     ", "     敵対者     ", "   結末(目的)   ",};
	char *notrune[/url] = {"", "<逆>",};
	int i;
	
	for(i = 0; i < ITEM; i ++){
		printf("  %s -> %s%s\n", item, card, notrune[!(rand() % 5)]);
	}
	putchar('\n');
	return;
}

int main(void)
{
	char *rune[/url] = {"知恵", "生命", "信頼", "勇気", "慈愛", "秩序", "至誠", "創造",
			"厳格", "治癒", "理性", "節度", "調和", "結合", "庇護", "清楚",
			"善良", "解放", "変化", "幸運", "意思", "誓約", "寛容", "公式",};
	
	srand((unsigned)time(NULL));
	do{
		Shuffle(rune, RUNE);
		Print(rune);
	}while(getch() != 0x0d);
	return 0;
}

 

フリオ

Re:カードをシャッフルするプログラム

#18

投稿記事 by フリオ » 18年前

 
 上のだと、カーソルキーを押したとき一気に二つ進んでしまうのでその対策として
関数、
int Quit(void)
{
	int ch;
	
	ch = getch();
	if(ch == 0x00) getch();
	return ch == 0x0d;
}
 
を追加して、
while(getch() != 0x0d);

while(!Quit());
に変更。
 

羽流布

Re:カードをシャッフルするプログラム

#19

投稿記事 by 羽流布 » 18年前

>フリオさん
 ありがとうございます。
 0x0d がエンターを表しているのですね? 私は Windows XP、BCC32 5.5.1 ですが、問題ありませんでした。
 以下は改良版です。
/*************************
 物語作成お助けプログラム
*************************/

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

#define NUM 24

// 関数のプロトタイプ
int Quit(void);

typedef struct {
	int flag;
	char *st;
} ram_t;

int main(void) {
	char *rune[NUM] = { "知恵", "生命", "信頼", "勇気",
			  "慈愛", "秩序", "至誠", "創造",
			  "厳格", "治癒", "理性", "節度",
			  "調和", "結合", "庇護", "清楚",
			  "善良", "解放", "変化", "幸運",
			  "意思", "誓約", "寛容", "公式" };
	ram_t ram[NUM];
	int i, j, n;
	
	printf("結果の単語から連想できるものを考え、物語を紡ぎ出すのです!\n");
	printf("(注)<逆>は意味が反対になります。\n\n");
	
	srand( (unsigned int)time(NULL) );
	
	for (i = 0; i < NUM; i++) {
		ram.st = rune;
		ram.flag = 0;
	}
	for (i = 0; i < 6; i++) {
		switch (i) {
			case 0: printf(" 主人公の現在―――〉"); break;
			case 1: printf(" 主人公の近い未来―〉"); break;
			case 2: printf(" 主人公の過去―――〉"); break;
			case 3: printf(" 援助者――――――〉"); break;
			case 4: printf(" 敵対者――――――〉"); break;
			case 5: printf(" 結末(目的)―――〉"); break;
		}
		j = NUM;
		n = rand() % j--;
		while (ram[n].flag == 1) {
			n = rand() % j--;
		}
		printf("%s", ram[n].st);
		ram[n].flag = 1;
		if ( (rand() % 10) < 3 ) {
			printf("<逆>");
		}
		printf("\n");
	}
	printf("\nエンターで終了\n");
	for (; ;) {
		if ( Quit() ) {
			return (0);
		}
	}
}

int Quit(void) {
	int ch;
	
	ch = getch();
	if (ch == 0x00) {
		getch();
	}
	return (ch == 0x0d);
}

バグ

Re:カードをシャッフルするプログラム

#20

投稿記事 by バグ » 18年前

BCCならば、それで大丈夫ですが、同じソースコードでもVisualStudioだと不具合が出ます。
ファンクションキーや矢印を押した時に最初に返ってくるコードが0x00ではなく、0xE0になるんですよね。
前に、これで随分と悩まされた事があったので、参考までに…(;^_^A

フリオ

Re:カードをシャッフルするプログラム

#21

投稿記事 by フリオ » 18年前

 
> 最初に返ってくるコードが0x00ではなく、0xE0になるんですよね。

 これですね。
http://support.microsoft.com/kb/57888/ja
 

羽流布

Re:カードをシャッフルするプログラム

#22

投稿記事 by 羽流布 » 18年前

>BCCならば、それで大丈夫ですが、同じソースコードでもVisualStudioだと不具合が出ます。
 でも、実行ファイルでプログラムを使う場合にはコンパイラは関係ないですよね?

Hermit

Re:カードをシャッフルするプログラム

#23

投稿記事 by Hermit » 18年前

>でも、実行ファイルでプログラムを使う場合にはコンパイラは関係ないですよね?
今回は関係なさそうですね(^^;
printf("エンターで終了\n");
	if ( scanf("%c", &ch) ) {
		return (0);
	}
の所は、
printf("エンターを押してください\n");
	getchar();
	return 0;
で良い気がする(^^;

羽流布

Re:カードをシャッフルするプログラム

#24

投稿記事 by 羽流布 » 18年前

>Hermitさん
 ありがとうございます。
 疑問が解消されました。

只今、学校の宿題に取り組んでいますので、また質問することになりそうです。

閉鎖

“C言語何でも質問掲示板” へ戻る