C言語で「棒消しゲームを作る予定です」

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
アバター
usao
記事: 1547
登録日時: 6年前

Re: C言語で「棒消しゲームを作る予定です」

#31

投稿記事 by usao » 5ヶ月前

> (2) 最初の64個の配列(二次元配列)が持つ意味や順序が分かりません……。

書いた人じゃないけど,まずここの部分がわからないときつそうなので,参考までに私の解釈を書いてみる.

program側の思考処理に
> 乱数で2つから1つ
という仕様があるために,
64パターンの盤面全てに関して,pragram側の打つ手の候補が2種類ずつ用意されている,ということでしょうね.
(1種類しか打つ手がない盤面に関しては { 0,0 } のように,同じものが2つ書かれている)

64パターンの盤面に 0~63 の通し番号を付与(※1)したとき,
例えば,盤面3番の状態でのprogram側が打つ手を決定する処理は,
【 pat[3](要素数2個の配列)に格納されている候補「5と6」のどちらか一方をランダムに選ぶ(※2)】


※1:この通し番号の付与方法が,
> (6) i = st[0]*32 + st[1]*16 + st[2]*8 + st[3]*4 + st[4]*2 + st[5];

※2:要素数2個の配列の要素のうちの一方をランダムで選ぶというのは要するに
配列[0] か 配列[1] のどちらかを選ぶということであるから,
添え字の部分を,乱数で0か1にできればよい.その方法として,
配列[ rand() % 2 ]
が用いられている.

アバター
angel-wing
記事: 17
登録日時: 5ヶ月前

Re: C言語で「棒消しゲームを作る予定です」

#32

投稿記事 by angel-wing » 5ヶ月前

To:usaoさま
ありがとうございます!
「64パターンの盤面全てに関して,pragram側の打つ手の候補が2種類ずつ用意されている」
なるほど、ここは実際に解いてみて考えます!
(6) i = st[0]*32 + st[1]*16 + st[2]*8 + st[3]*4 + st[4]*2 + st[5];
[ rand() % 2 ]
がかかわってくるということなのですね!
単純そうで、めちゃくちゃ深い……私じゃ100年経っても届かない!

かずま

Re: C言語で「棒消しゲームを作る予定です」

#33

投稿記事 by かずま » 5ヶ月前

angel-wing さんが書きました:
5ヶ月前
(1) return 0と1が使い分けられていますが、何か意図があるのでしょうか(全て0にしてみてもエラーは出ない様子ですが)。
conv は、入力が正しい時 1 を返し、正しくない時 0 を返します。
game は、fgets で EOF を検出したとき 1 を返し、
もうこれ以上入力がないと判断しプログラムの実行を終了します。
入力があった場合は 0 を返し、main で終了の確認を行います。
angel-wing さんが書きました:
5ヶ月前
(2) 最初の64個の配列(二次元配列)が持つ意味や順序が分かりません……。
main の st は「棒の状態(state)」を表します。
int st[6] = { 1, 1,1, 1,1,1 }; で 6本の棒はすべてあります。
プレイヤーが 1番上の棒を消すと
st が { 0, 1,1, 1,1,1 } になります。
これを 2進数の 011111 とすると、その値は 31 です。
pat[31] は { 4, 6 } です。
これは、コンピュータの次の手が 4 または 6 であることを示します。
どちらにするかは、乱数で決めます。

最初にプレイヤーが 456 を消すと、
st が { 1, 1,1, 0,0,0 } になります。
これを 2進数の 111000 とすると、その値は 56 です。
pat[56] は { 23, 23 } です。
これは、コンピュータの次の手が 23 であることを示します。
乱数の値にかかわらず、23 になります。
angel-wing さんが書きました:
5ヶ月前
(3) int game内の if (!fgets(buf, sizeof buf, stdin)) return 0;
 には、どのような意味がありますか。「stdinでキーボードから文字列を受け取り、関数冒頭で定義したbufに当てはめてサイズを取得する→もしサイズが取得できなければ、printfの内容を表示する」という解釈で大丈夫でしょうか……。
char buf[256]; と宣言されているので、sizeof buf は 256 です。
fgets(buf, 256, stdin) と書くのと同じです。
fgets は通常 buf の値、すなわち buf[0] のアドレスを返します。
しかし、EOF に遭遇するなどのエラーがあった場合、NULL を返します。
「!アドレス」は 0 であり、「!NULL」は 1 です。
だから、fgets が EOF に遭遇したとき、return します。
angel-wing さんが書きました:
5ヶ月前
(4) int user以下で、if (conv(i, user) == 0) { puts("エラー"); continue; }
 ですが、「userの入力が0ならば、エラー表示をする」ように読めたのですが、実際は消し方が違う場合(例えば12のように縦に消したり)に出るエラーだと思います。このプログラムの0はどのような意味なのでしょうか。
int user[6] = { 0 }; で user は { 0, 0, 0, 0, 0, 0 } と初期化されます。
i にはプレイヤーの入力が入っています。例えば 23 とします。
conv(23, user) で conv を呼び出すと、conv の中で
user[1] = user[2] = 1 が実行され、
user は { 0, 1, 1, 0, 0, 0 } となります。
これはプレーヤが消したい棒を表しています。
return user[1] = user[2] = 1; なので、
conv は 1 を返します。
conv が 0 を返さなかったので、{ puts("エラー"); continue; } は
実行されません。

プレイヤーの入力が例えば 34 であったとすると、
conv は 0 を返します。
0 だったらエラー表示です。
angel-wing さんが書きました:
5ヶ月前
(5) (4)の続き、if (user[ i] == 1) if (st[ i] == 0) i = 9; else st[ i] = 0;
 ここの3行ですが、「userが入力した配列が1の場合、または配列の中が0になった場合かiが9の場合、もしくは配列内が0の場合、iが5になるまで繰り返す(for)」と読みましたが、違いますよね……どのように解釈すればよいのでしょうか。
user の値が { 0, 1, 1, 0, 0, 0 } だったとすると、
user[1] == 1 ですから、2 の棒を消したいということです。
st[1] == 0 だと、2の棒は既に消されているので、エラーです。
i を 6以上の値にして、for文の i++ で 6 より大きい値にして
forループを終了させます。
st[1] が 1 だと、2の棒があるので、st[1] = 0; でその棒を消します。

user[2] == 1 は 3の棒を消したいということで、以下同様。
user[ i] が 0 の時は、棒を消しません。
angel-wing さんが書きました:
5ヶ月前
(6) i = st[0]*32 + st[1]*16 + st[2]*8 + st[3]*4 + st[4]*2 + st[5];
 ここがプログラムの中枢計算だと思うのですが、これはおそらく「64パターンを2進数に分解して和を求めている」ということでしょうか。
 すると、次のi = pat[ i][rand() % 2]; は、「和が偶数の場合」の処理ということでしょうか。これは凄いですね……。
st の値が { 0, 1,1, 1,1,1 } だと、これを 2進数の 011111 だと解釈して
i = 0*32 + 1*16 + 1*8 + 1*4 + 1*2 + 1 = 31 という値を得ます。
st の値が { 1, 1,1, 0,0,0 } だと、これを 2進数の 111000 だと解釈して
i = 1*32 + 1*16 + 1*8 + 0*4 + 0*2 + 0 = 56 という値を得ます。
これらの値は pat の何番目を選択するかということに使います。
rand() % 2 は乱数を 2で割った余りなので、0 または 1 です。
pat の i番目は 2つの値があるので、どちらかを乱数で選ぶことになります。
angel-wing さんが書きました:
5ヶ月前
(7) int mainの、while (game() == 0) {
 というのは、「game関数が0の場合→ゲームが終わった場合」という意味でしょうか。
game が 0 を返すのは、勝敗がついた(プレイヤーが負けた)時です。
game が 0以外を返すのは、game の中でエラーがあった時です。
angel-wing さんが書きました:
5ヶ月前
(8) 最後の、if (!fgets(buf, sizeof buf, stdin) || buf[0] != 'r') break;
 というのは、「文字列がサイズを持たない場合(勝負がついた場合?) or 'r’が入力されない場合は、ゲームを終了する(main関数を抜ける)」という意味でしょうか。
EOFの入力などで行入力が失敗したか、または 'r' が入力されない場合は、
whileループを終了する、という意味です。

アバター
angel-wing
記事: 17
登録日時: 5ヶ月前

Re: C言語で「棒消しゲームを作る予定です」

#34

投稿記事 by angel-wing » 5ヶ月前

To:かずま様

おはようございます!
私などでも分かるようにご丁寧に解説していただき、本当に感謝です!
最初の配列から始まって、すごく深いプログラムですね。完成度がやばみです。
webで調べても分からなかったことが解決して、とても勉強になりました!
本当にありがとうございました。もっとこのプログラムの研究をします!!

かずま

Re: C言語で「棒消しゲームを作る予定です」

#35

投稿記事 by かずま » 5ヶ月前

かずま さんが書きました:
5ヶ月前
これでは continue; で for文に戻ってしまいますね。
次のように訂正します。

コード:

		for (i = 0; i < 6; i++)
			if (user[i] == 1)
				if (st[i] == 0) i = 9;
				else st[i] = 0;
		if (i > 6) { puts("  0 は消せません"); continue; }
この訂正は正しくありませんでした。

コード:

    1        1
   2 3      1 1
  4 5 6    1 1 1

あなたの番です。どれを消しますか? 3

    1        1
   2 3      1 0
  4 5 6    1 1 1

コンピュータの番です。Enter を押してください

コンピュータは 56 を消します。

    1        1
   2 3      1 0
  4 5 6    1 0 0

あなたの番です。どれを消しますか? 23
  0 は消せません
あなたの番です。どれを消しますか? 2
  0 は消せません
あなたの番です。どれを消しますか?
3 が 0 なので 23 の入力で「0 は消せません」というのはいいんですが、
その後 2 を入力しても「0 は消せません」と表示されてしまいます。
23 の入力の時、2 を消してしまっているからです。
次のように修正します。

コード:

		for (i = 0; i < 6; i++)
			if (user[i] == 1 && st[i] == 0) break;
		if (i < 6) { puts("0 は消せません"); continue; }
		for (i = 0; i < 6; i++)
			if (user[i] == 1) st[i] = 0;

アバター
angel-wing
記事: 17
登録日時: 5ヶ月前

Re: C言語で「棒消しゲームを作る予定です」

#36

投稿記事 by angel-wing » 5ヶ月前

To:かずま様

おはようございます!
試してみたら、確かに「2」が消せないエラー(バグ)が!
全く気づきませんでした……私にテスターはむりぽ~。
訂正ありがとうございます!!

返信

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