ジャンケンプログラムを作ろう!

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

ジャンケンプログラムを作ろう!

#1

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

前のトピの回答の続きとして、書きました。

まだC言語でゲームを作ったことの無い人、「プログラムを賢く書く」ってどういうことかわからない人、プログラムの面白さを実感した事が無い人、

是非このトピの内容をご覧下さい^^

プログラム独特の考え方の紹介、ゲームプログラムの紹介をいたします☆

必要知識として

・scanf
・printf
・if文
・配列、文字列

の使い方さえわかっていれば大丈夫です!

ではまず、ジャンケンをするのにプレイヤーの意思を受け取る部分を作ってみましょう。
 
 
 
#include <stdio.h>

int main(){
	int you;

	printf("ジャンケン・・・。\n\n");
	printf("0:グー 1:チョキ 2:パー\n\n");

	scanf("%d",&you);

	printf("あなたの入力:%d\n\n",you);

	return 0;
}
 
 
プレイヤーの入力情報を格納するyou変数を用意し、そこにscanfで取り込んでいるだけです。
後はprintfで表示を行っているだけです。


コンピュータにジャンケンを出させてみましょう。
プレイヤーは0~2の数字を入力してそれぞれ表現しましたね。
コンピュータにも0~2の数字を出させて見ましょう。

ランダムな数というのはゲームにとって最重要ポイントです。
「毎回同じ結果になる」ゲームはゲームとは言いません。
人間はいつも違う結果を残すからゲームになるわけですね。

ここでランダムな数を作るにはrand()関数を使います。
これは最初に
#include <stdlib.h>
と書くことで使えるようになる関数です。
rand()と書くとここの部分はその時によって0~32767の数に代わります。
つまり
int a;
a = rand();
とかくと、aには0~32767までの数字のどれかが入る事になります。

この数字を使って0~2の数字に変換するには?
「割ったあまり」という考え方をここで出してきます。
+-*/という四則演算子以外に%という割ったあまりを計算する演算子があります。

a = 5 % 2;

と書くと、5を2で割ったあまりが入ります。つまり1です。

a = 5 % 3;

と書くと、5を3で割ったあまりが入ります。つまり2です。
ここで連続で見てみましょう。

a = 0 % 3; ---->このとき a=0
a = 1 % 3; ---->このとき a=1
a = 2 % 3; ---->このとき a=2
a = 3 % 3; ---->このとき a=0
a = 4 % 3; ---->このとき a=1
a = 5 % 3; ---->このとき a=2
a = 6 % 3; ---->このとき a=0
a = 7 % 3; ---->このとき a=1
a = 8 % 3; ---->このとき a=2
a = 9 % 3; ---->このとき a=0

aの値が0,1,2,0,1,2,とループしている事に気が付きましたか?
そうこのように書くことで、xで割ったあまりを計算するとx種類の数が生成出来るのです。

つまり

com = rand() % 3;

と書くことで、0~2の数が生成できますね。これを利用してみましょう。
 
 
#include <stdio.h>
#include <stdlib.h>

int main(){
	int you,com;

	printf("ジャンケン・・・。\n\n0:グー 1:チョキ 2:パー\n\n");

	scanf("%d",&you);

	com = rand()%3;

	printf("あなた\t\t%d\nコンピュータ\t\t%d\n\n",you,com);

	return 0;
}

実行結果

ジャンケン・・・。

0:グー 1:チョキ 2:パー

1
あなた          1
コンピュータ         2
 
 
おぉ、これでゲームらしくなってきたな・・・
と思ったら間違いです。
これを何度も実行してみましょう。

コンピュータ 2
コンピュータ 2
コンピュータ 2
コンピュータ 2
コンピュータ 2

・・・あれ?コンピュータは2しか出さないぞ?
と気が付くと思います。そうなんです、コンピュータは「言った事しかしない。正確にその事しかしない」ので、「だいたいな数を出せ、ランダムな数を出せ」というのはホント苦手です。
ですから、ランダムな数にさせる「きっかけ」を持たせないといけないのです。

実行するたびに変わる数って何かいいものあるんだろうか・・。と考えると
実行するたびに変わる数で一ついいものがありますね。
そう、実行した瞬間の時刻です。
「○○日○○時間○○分○○秒」
という今の時刻の事ですね。おそらく前回実行したときと今回実行した時ではこの数は異なっているはずです。
これにきっかけをもたせてランダムな数を作らせたらいいでしょう。

それは

srand((unsigned)time(NULL));

を最初に書くと実現できます。
この関数は最初に
#include <time.h>
を書くことで使用できるようになります。
「ん・・なんか難しいな」
とお思いでしょうが、別に意味は知らなくてもOKです。
これを書けば乱数が質のよい乱数になるんだなと覚えて置いてください。

プログラムにまとめると以下のようになります

管理人

Re:ジャンケンプログラムを作ろう!

#2

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

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

int main(){
	int you,com;

	srand((unsigned)time(NULL));

	printf("ジャンケン・・・。\n\n0:グー 1:チョキ 2:パー\n\n");

	scanf("%d",&you);

	com = rand()%3;

	printf("あなた\t\t%d\nコンピュータ\t\t%d\n\n",you,com);

	return 0;
}


実行結果

ジャンケン・・・。

0:グー 1:チョキ 2:パー

1
あなた          1
コンピュータ         0
 

 
先ほどと違ってコンピュータは毎回違う数字を出してきますね。
では次にこの数字を使ってじゃんけんをしてみましょう。

ジャンケンをして、あいこの時のみもう一度ジャンケンをして、
まだ勝ち負けの判定をせずに終えるプログラムを作ってみます。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(){
	int you,com;

	srand((unsigned)time(NULL));

	printf("ジャンケン・・・。\n\n0:グー 1:チョキ 2:パー\n\n");

	while(1){
		scanf("%d",&you);

		com = rand()%3;

		printf("あなた\t\t%d\nコンピュータ\t\t%d\n\n",you,com);
		
		if(you==com){
			printf("あいこで・・・。\n\n0:グー 1:チョキ 2:パー\n\n");
			continue;
		}
		else{
			break;
		}
	}
	return 0;
}

実行結果

ジャンケン・・・。

0:グー 1:チョキ 2:パー

1
あなた          1
コンピュータ         1

あいこで・・・。

0:グー 1:チョキ 2:パー

2
あなた          2
コンピュータ         2

あいこで・・・。

0:グー 1:チョキ 2:パー

1
あなた          1
コンピュータ         1

あいこで・・・。

0:グー 1:チョキ 2:パー

0
あなた          0
コンピュータ         0

あいこで・・・。

0:グー 1:チョキ 2:パー

1
あなた          1
コンピュータ         2
 
 
while(1)というのは無限ループのことでbreak文を書かない限り永遠にループし続けます。

continue文、break文はよろしいでしょうか?
continueを書くとその時点でループの最初に戻り
breakを書くとその時点でループから抜けます。

you==comの時はあいこですから、この時もう一度ジャンケンをさせているわけですね。
あいこの間は延々とループが繰り返されます。

今回ジャンケンプログラムを紹介する醍醐味はこの次です!!

どうやって勝ち負けを判定させるか

そりゃ

if(プレイヤーがグー かつ コンピュータがチョキ)
  勝ち;
if(プレイヤーがチョキ かつ コンピュータがグー)
  負け;

・・・

と全てのパターンについて条件を書いていってもプログラムは完成します。
しかしジャンケンではなく、1万通りも2万通りも条件があるようなプログラムだったらこのような書き方が通るでしょうか?
そう、「プログラムは賢く書く」という必要があるんです。

ここでジャンケンはどのような性質を持っているか考えます。
0:グー 1:チョキ 2:パー
として、

プレイヤー	コンピュータ
0	1	->勝ち
1	2	->勝ち
2	0	->勝ち

1	0	->負け
2	1	->負け
0	2	->負け
 
 
ここでみてみると、0,1,2,0,1,2...というループで考えると自分が出した数字の次の数字がコンピュータの出した数字と同じ時「勝ち」でその反対が「負け」になっていることがわかります。

0,1,2,0,1,2というループは3で割ったあまりを計算する事で変換できるんでしたよね。

つまり

(自分の出した数 + 1) % 3 がコンピュータの出した数だったら勝ち!であることがわかります。

あいこでもないし、上記勝ちでもない時は「負け」であることがわかりますね。
この事をプログラムで書いてみましょう。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(){
	int you,com;

	srand((unsigned)time(NULL));

	printf("ジャンケン・・・。\n\n0:グー 1:チョキ 2:パー\n\n");

	while(1){
		scanf("%d",&you);

		com = rand()%3;

		printf("あなた\t\t%d\nコンピュータ\t\t%d\n\n",you,com);
		
		if(you==com){
			printf("あいこで・・・。\n\n0:グー 1:チョキ 2:パー\n\n");
			continue;
		}
		else if((you+1)%3==com){
			printf("あなたの勝ちです!\n\n");
			break;
		}
		else{
			printf("あなたの負けです!\n\n");
			break;
		}
	}
	return 0;
}

実行結果

ジャンケン・・・。

0:グー 1:チョキ 2:パー

2
あなた          2
コンピュータ         0

あなたの勝ちです!
 
 
ほら、自分がパーに対してコンピュータがグーですからちゃんと「勝ち」と判定してくれましたよ!

これでジャンケンプログラムの完成ですね!
しかし

あなた 2
コンピュータ 0

という表示ではちょっとあじけないので、文章で出させて見ましょう。
今せっかく0がグー、1がチョキ、2がパーという対応関係があるのですから
この対応関係を文字列とも関連付けて見ましょう。

char jan[3][7]={"グー","チョキ","パー"};

このように文字列を用意します。
今回一番長い語句は「チョキ」です。全角3文字なので6バイト用意する必要があり、文字列は最後に終端記号が1バイトはいるので7バイト用意する必要があります。
この7バイトの文字列格納配列を3つ用意します。

jan[0]には「グー」
jan[1]には「チョキ」
jan[2]には「パー」

が入っていますから、先ほどの対応関係とぴったりですね。
これをプログラムに書いてみましょう。

以下のプログラムは本当に完成したプログラムです

管理人

Re:ジャンケンプログラムを作ろう!

#3

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

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

int main(){
	int you,com;
	char jan[3][7]={"グー","チョキ","パー"};

	srand((unsigned)time(NULL));

	printf("ジャンケン・・・。\n\n");
	
	while(1){
		printf("0:グー 1:チョキ 2:パー\n\n");
	
		scanf("%d",&you);

		com = rand()%3;

		printf("あなた\t\t%s\nコンピュータ\t\t%s\n\n",jan[yo[/url],jan[com]);
		
		if(you==com){
			printf("あいこで・・・。\n\n");
			continue;
		}
		else if((you+1)%3==com){
			printf("あなたの勝ちです!\n\n");
			break;
		}
		else{
			printf("あなたの負けです!\n\n");
			break;
		}
	}
	return 0;
}


実行結果

ジャンケン・・・。

0:グー 1:チョキ 2:パー

1
あなた          チョキ
コンピュータ         チョキ

あいこで・・・。

0:グー 1:チョキ 2:パー

1
あなた          チョキ
コンピュータ         パー

あなたの勝ちです!
 
 
どうでしょう?ゲームらしいゲームが作成できたと思います。
このように

・割ったあまりを計算する事である区間の数字のループを作る

とか

・勝ち負け判定を勝負の性質を考える事で実装を容易にする

という事はとても大事な事です。
今回の記事でそれが少しだけでもわかってもらえたら光栄です☆

バグ

Re:ジャンケンプログラムを作ろう!

#4

投稿記事 by バグ » 18年前

では、私も初心者向けな『お手軽神経衰弱』のプログラムを載せてみますね。
/*****************************************************************************/
/* お手軽神経衰弱♪(スクロールさせたらカンニングできます(笑))                */
/* 2007.03.27 Programing By Bug                                              */
/*****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

// カードの表示用関数
void DrawCard( int Card[ 4 ][ 13 ] , int X[ 2 ] , int Y[ 2 ] )
{
	int i , j;
	char *pMark[ 13 ] = { "A" , "2" , "3" , "4" , "5" , "6" , "7" , "8" , "9" , "10" , "J" , "Q" , "K" };

	for( j = 0 ; j < 4 ; j++ ) {
		for( i = 0 ; i < 13 ; i++ ) {
			if( i == X[ 0 ] && j == Y[ 0 ] || i == X[ 1 ] && j == Y[ 1 ] ) {		// X,Yで指定された座標と一致したらカードの数字を表示
				printf( "%s" , pMark[ Card[ j ][ i ] ] );
			}
			else if( Card[ j ][ i ] == 13 ) {										// 既に取得した場所ならば空白表示
				printf( "□" );
			}
			else {																	// 上記以外ならば、伏せて表示
				printf( "■" );
			}
		}
		printf( "\n" );
	}
	printf( "\n" );
}

// メイン関数
int main()
{
	int i , j , Get , Cnt , A[ 2 ] , B[ 2 ] , X[ 2 ] , Y[ 2 ] , Card[ 4 ][ 13 ];

	srand( ( unsigned )time( NULL ) );

	// カウントを0で初期化する
	Get = 0;
	Cnt = 0;

	// カードの初期化
	for( j = 0 ; j < 4 ; j++ ) {
		for( i = 0 ; i < 13 ; i++ ) {
			Card[ j ][ i ] = i;
		}
	}

	// カードをシャッフルする( とりあえず1000回 )
	for( i = 0 ; i < 1000 ; i++ ) {
		// 入れ替え用の座標の準備
		A[ 0 ] = rand() % 4;
		A[ 1 ] = rand() % 4;
		B[ 0 ] = rand() % 13;
		B[ 1 ] = rand() % 13;

		// カードを入れ替える
		j = Card[ A[ 0 ] ][ B[ 0 ] ];
		Card[ A[ 0 ] ][ B[ 0 ] ] = Card[ A[ 1 ] ][ B[ 1 ] ];
		Card[ A[ 1 ] ][ B[ 1 ] ] = j;
	}

	// A,B配列へダミー座標を代入( 全裏返し表示の為 )
	A[ 0 ] = -1;
	A[ 1 ] = -1;

	// 全てのカードをGetするまでループする
	while( Get < 26 )
	{
		// 全て裏返し状態でカードを表示する為にダミー座標を使用する
		DrawCard( Card , A , A );
		Cnt++;
		printf( "%d手目\n" , Cnt );

		// 裏返す場所を指定する
		for( i = 0 ; i < 2 ; i++ ) {
			printf( "%d枚目のカードの位置を指定してください\n" , i + 1 );
			printf( "X( 0 ~ 12 ) = " );
			scanf( "%d" , &X[ i ] );
			printf( "Y( 0 ~  3 ) = " );
			scanf( "%d" , &Y[ i ]  );

			// 既に開かれている場所や、無効な座標が入力されたら入力をしなおす
			if( X[ i ] < 0 || X[ i ] > 12 || Y[ i ] < 0 || Y[ i ] > 3 || Card[ Y[ i ] ][ X[ i ] ] == 13 ) {
				printf( "Error!!\n" );
				i--;
				continue;
			}

			// 入力された2箇所の座標が同じ場所であれば入力をしなおす
			if( i == 1 && X[ 0 ] == X[ 1 ] && Y[ 0 ] == Y[ 1 ] ) {
				printf( "Error!!\n" );
				i--;
				continue;
			}
		}

		// 入力された場所を裏返してカードを表示する
		DrawCard( Card , X , Y );

		// 裏返した2枚が同じ物だったらGetカウントをインクリメントして、2箇所の数値を13( 取得済み )にする
		if( Card[ Y[ 0 ] ][ X[ 0 ] ] == Card[ Y[ 1 ] ][ X[ 1 ] ] ) {
			printf( "Nice Get!!\n" );
			Get++;
			Card[ Y[ 0 ] ][ X[ 0 ] ] = 13;
			Card[ Y[ 1 ] ][ X[ 1 ] ] = 13;
		}
	}

	printf( "\nお疲れ様でした…\n" );
	printf( "%d手でクリアです!!" , Cnt );

	return( 1 );
}
開発環境はVC++6.0ですが、あまり変わった事はしていないので、おそらくどの環境でもいけるかと思います…保障はできませんが…(^_^;)

閉鎖

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