ページ 1 / 1
カードをシャッフルするプログラム
Posted: 2007年6月07日(木) 19:22
by 羽流布
またまた質問に来ました<うるふ>です。
私は小説を書いているのですが、とある本でカードを利用した物語作成術が載っておりました。そこで、これを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:カードをシャッフルするプログラム
Posted: 2007年6月07日(木) 19:52
by フリオ
カードをシャッフルするプログラムです。
#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:カードをシャッフルするプログラム
Posted: 2007年6月07日(木) 19:54
by 管理人
シャッフルについてはフリオさんが紹介なさっていますので、
同じものを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:カードをシャッフルするプログラム
Posted: 2007年6月07日(木) 21:33
by 羽流布
>フリオさん
ありがとうございます^^
変数宣言の中括弧は不必要だったのですね。
そこら辺も曖昧でしたので助かりました。
>管理人さん
ポインタはまだ習っていませんが、構造体は今週学んだばかりです。
構造体って、こんな使い方が出来るんですね。全く考えていませんでした……。
というわけで、構造体のほうでプログラムを作り変えてみます。
Re:カードをシャッフルするプログラム
Posted: 2007年6月07日(木) 21:59
by 羽流布
>フリオさん
j = rand() % i --; の意味が理解できないのですが……?
Re:カードをシャッフルするプログラム
Posted: 2007年6月07日(木) 23:45
by 羽流布
下記のプログラムを実行したら、
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)) );
}
Re:カードをシャッフルするプログラム
Posted: 2007年6月08日(金) 00:00
by Hermit
問題の行は、
strcpy(ram.st,rune);
としておけば良いようです。
又は、構造体を
typedef struct {
int flag;
char *st;
} ram_t;
にしてあれば、そのまま
ram.st=rune;
でもいいでしょう。
Re:カードをシャッフルするプログラム
Posted: 2007年6月08日(金) 00:00
by box
> 32: 左辺値が必要(関数 main )
strcpy() を使ってください。
Re:カードをシャッフルするプログラム
Posted: 2007年6月08日(金) 00:12
by 羽流布
皆さん、ありがとうございますm(_ _)m
どうやら、ポインタが絡んでくるようですね。
私はまだ習っていないので、よく分かりませんが……。
とりあえず、これで目的のプログラムは完成しました。
欲を言えば、<逆>を表示する確率を変更できればいいのですが、無理なんでしょうね↓↓
Re:カードをシャッフルするプログラム
Posted: 2007年6月08日(金) 00:22
by Hermit
普通に、rand() 使えばいいだけではないでしょうか。
if ((rand() % 10) > 6) printf("<逆>");
例えばこれなら 30% 程度かな。
if (rand() < (RAND_MAX /4)) printf("<逆>");
だったら、25 % くらい
Re:カードをシャッフルするプログラム
Posted: 2007年6月08日(金) 00:34
by 羽流布
>Hermitさん
こうすれば確率を調整できるのですか。
ありがとうございますm(_ _)m
でも、実行してみると全部<逆>になったりと……なんか前より極端になった気が……。
まぁ、0か1かという乱数ですから、確率なんてあんまり意味ない気がしてきました○TZ
Re:カードをシャッフルするプログラム
Posted: 2007年6月08日(金) 01:04
by 管理人
先日から
「ポインタ」=「やめる、避ける」
と嫌ってらっしゃるようですが、
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:カードをシャッフルするプログラム
Posted: 2007年6月08日(金) 11:36
by 羽流布
>管理人さん
そうですよね、未知なる物を嫌ってはいけませんよね。
好奇心を持って取り組みたいと思います^^
i++ とかの ++ の部分は今まであまり意識せずに使っていました。
なので、参考になりました。
ありがとうございますm(_ _)m
Re:カードをシャッフルするプログラム
Posted: 2007年6月08日(金) 12:23
by 羽流布
皆さんの意見を参考にプログラムを改良してみました^^
でも、実際にツールとして使うためには、プログラムの終了条件に「エンターを押して終了」などの処理が必要だということに気付きました。
そこで、エンターの文字列表現が知りたいのですが、改行の表現では上手くいかないようです……。どのように表現すればいいのでしょうか?
/*************************
物語作成お助けプログラム
*************************/
#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:カードをシャッフルするプログラム
Posted: 2007年6月08日(金) 13:02
by バグ
環境によって違う場合もありますので、下記のようなプログラムで調べてみてはいかがでしょうか?
押したキーのコードを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:カードをシャッフルするプログラム
Posted: 2007年6月08日(金) 13:58
by 羽流布
一応、Dと出ましたけど、これって環境によって違うということですか?
それでは、実用的ではない気がするのですが……?
どうしましょ○TZ
Re:カードをシャッフルするプログラム
Posted: 2007年6月08日(金) 16:24
by フリオ
こういうことでしょうか。
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:カードをシャッフルするプログラム
Posted: 2007年6月08日(金) 17:56
by フリオ
上のだと、カーソルキーを押したとき一気に二つ進んでしまうのでその対策として
関数、
int Quit(void)
{
int ch;
ch = getch();
if(ch == 0x00) getch();
return ch == 0x0d;
}
を追加して、
while(getch() != 0x0d);
を
while(!Quit());
に変更。
Re:カードをシャッフルするプログラム
Posted: 2007年6月08日(金) 17:57
by 羽流布
>フリオさん
ありがとうございます。
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:カードをシャッフルするプログラム
Posted: 2007年6月08日(金) 22:02
by バグ
BCCならば、それで大丈夫ですが、同じソースコードでもVisualStudioだと不具合が出ます。
ファンクションキーや矢印を押した時に最初に返ってくるコードが0x00ではなく、0xE0になるんですよね。
前に、これで随分と悩まされた事があったので、参考までに…(;^_^A
Re:カードをシャッフルするプログラム
Posted: 2007年6月08日(金) 23:18
by フリオ
> 最初に返ってくるコードが0x00ではなく、0xE0になるんですよね。
これですね。
http://support.microsoft.com/kb/57888/ja
Re:カードをシャッフルするプログラム
Posted: 2007年6月09日(土) 03:28
by 羽流布
>BCCならば、それで大丈夫ですが、同じソースコードでもVisualStudioだと不具合が出ます。
でも、実行ファイルでプログラムを使う場合にはコンパイラは関係ないですよね?
Re:カードをシャッフルするプログラム
Posted: 2007年6月09日(土) 09:25
by Hermit
>でも、実行ファイルでプログラムを使う場合にはコンパイラは関係ないですよね?
今回は関係なさそうですね(^^;
printf("エンターで終了\n");
if ( scanf("%c", &ch) ) {
return (0);
}
の所は、
printf("エンターを押してください\n");
getchar();
return 0;
で良い気がする(^^;
Re:カードをシャッフルするプログラム
Posted: 2007年6月09日(土) 16:08
by 羽流布
>Hermitさん
ありがとうございます。
疑問が解消されました。
只今、学校の宿題に取り組んでいますので、また質問することになりそうです。