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

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

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

#1

投稿記事 by angel-wing » 5年前

[1] 質問文
 [1.1] 棒消しゲームを1週間で作る(C言語)
 [1.2] 上から三段  a
             b c
d e f
     で構成し、最初は全てに1を代入。選択した箇所を0に変えていく予定です(後手必勝のプログラム)。

 [1.3] 現状     1
1 1
1 1 1
      を表示させる所までできました。この後の進み方で困っています。
      一応、Userの手→Programの手→Userの手については、if文を使って(場合によってはランダム関数?)            を使って、全通り組んでしまう予定です。
      
 [1.4] 今何がわからないのか→Userが手を選択する場合、以下を考えています(優先順位順)。
     1.C言語で画面上にマウスで線を引く
     2.チェックボタンで選択肢を用意する
     3.文章で選択肢を用意する
  ところが、二点間の線引きについては、C++とかの説明はありますが、普通のC言語では見当たりませんでした。
     簡単に作れましたらご教授いただければ助かります。
     座標を用意したり大変そうならば、2か3で進める予定です。

[2] 環境  
 [2.1] OS : Windows7-64bit
 [2.2] [苦しんで覚えるC言語]さんより、「学習用C言語開発環境 Ver 0.0.9.0」使用
[3] その他
 ・独学で始めて1週間ほど、まだ「新C言語入門/スーパービギナー編」を読破したばかりです。
 ・コンパイラ:Tiny C Compiler、テキストエディタ: Azuki

アバター
usao
記事: 1887
登録日時: 11年前

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

#2

投稿記事 by usao » 5年前

> ところが、二点間の線引きについては、C++とかの説明はありますが、普通のC言語では見当たりませんでした。

この文章だけでは,何が困り事なのかがちょっとわかりませんが…


マウスの操作状況を取得できる状態にあるのならば,
「マウスで引いた線の形」=「マウスボタンを押しから離すまでの間にマウスカーソルが通った座標群(つまり折れ線)」
をデータとして蓄積することは可能かと思います.
であれば,折れ線を構成する各線分が各々の「棒」と交わったか否かを判定すれば良いのではないでしょうか.

その他の入力方法としては,
{何段目の,何本目から,何本目まで}という形で3つの値で入力させれば良いように思います.

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

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

#3

投稿記事 by angel-wing » 5年前

To:usaoさん
レスありがとうございます。

「マウスの操作状況を取得できる状態」
→マウスはPCに繋がっていますが、「操作状況を取得」の仕方が分かりません……。
  上記のキーワードで検索してみます。

「座標群(つまり折れ線)をデータとして蓄積」
→これも、メモ帳のようなページにどうやって座標を与えるのか、入門書になかったので
  やはり上記のキーワードで検索してみます。

「何段目の,何本目から,何本目まで}という形で3つの値で入力」
→これは何となくイメージがつきますが、具体的にscanfやprintfをどう使うのか分かりません。
  配列を[0]~[5]まで決めて、\nで改行するだけでは足りないでしょうか。

現在、とりあえず動くものを、ということでペタペタ書いています。
でも、選択した箇所を0に表示できなくて、止まっています。
なにぶん初めてなのでヘンテコリンですみません。

コード:

 

#include <stdio.h>

int main(void)
{
	int a,b,c,d,e,f;
	a=1, b=1, c=1, d=1, e=1, f=1;
	
	printf(" 【線消しゲームへようこそ】\n\n");
	printf(" 選択 → 結果\n");
	printf("    a    %d\n", a);
	printf("   b c   %d %d\n", b,c);
	printf("  d e f  %d %d %d\n\n", d,e,f);
	
	printf("  さぁ、あなたの番ですよ!\n\n");
	printf("  コマンド?\n\n");
	
	printf("・aを消す→1を入力\n");
	printf("・bを消す→2を入力\n");
	printf("・cを消す→3を入力\n");
	printf("・dを消す→4を入力\n");
	printf("・eを消す→5を入力\n");
	printf("・fを消す→6を入力\n");
	printf("・bcを消す→23を入力\n");
	printf("・deを消す→45を入力\n");
	printf("・efを消す→56を入力\n");
	printf("・defを消す→456を入力\n\n\n");

	int user;
	scanf("%d", &user);
	printf("%d\n", user);
	if (user = 1) {int a = 0;}	
	else if (user = 2) {int b = 0;}
	else if (user = 3) {int c = 0;}
	else if (user = 4) {int d = 0;}
	else if (user = 5) {int e = 0;}
	else if (user = 6) {int f = 0;}
	
	printf(" 選択 → 結果\n");
	printf("    a    %d\n", a);
	printf("   b c   %d %d\n", b,c);
	printf("  d e f  %d %d %d\n\n", d,e,f);
	
	return 0;
}

 

アバター
usao
記事: 1887
登録日時: 11年前

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

#4

投稿記事 by usao » 5年前

マウスとかチェックボタンとか書いてあったので
何かGUIが存在する世界で作っているのかと思ったのですが,
printfやscanfを使うような形態(所謂コンソールアプリ)なのですね.

であれば,とりあえず,マウスとかの話はやめてscanfで入力する物を考えると良いかと思います.
単純に,scanfを3回使って
> {何段目の,何本目から,何本目まで}
を入力させてはいかがでしょうか.

で,それはそれとして(?),棒の状態を表す変数が a,b,c,d,e,f という現在の実装だと,
そういった入力に対しての処理を実装するのがとても困難になるかと.
複数の棒の状態を個別のa~fで表現するのではなく,配列で表現すると良いかと思います.
例えば,

コード:

//6本(3段なので 1+2+3 本)の棒の状態.
//初期状態を表す値が1で,消された棒は値を0にする.
int StickState[6] = { 1,  1,1,  1,1,1 };
とするならば,
・1段目の棒の状態を表す配列要素は StickState[0]
・2段目の〃は StickState[1], StickState[2]
・3段目の〃は StickState[3], StickState[4], StickState[5]
ですから,各段の最も左側の棒の状態を表す配列要素のindexを

コード:

//RowIndexで指定した段の先頭(最も左)の棒のindexを求む
//※RowIndexの値は0-based.すなわち,最上段(1段目)のときの値は0.
int StartIndexOfRow( int RowIndex )
{
	int ret = 0;
	for( int i=1; i<=RowIndex; ++i ){	ret += i;	}
	return ret;
}
等のようにして,計算で求めることができますよね.
「3段目の2本目」とか指定された際の,その棒の状態を表す配列要素は

コード:

//※段数も,段内の「何本目」という値も,共に0-basedで扱えば,
//3段目→2 , 2本目→1
StickState[  StartIndexOfRow(2) + 1 ]
になります.

angel-wingです

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

#5

投稿記事 by angel-wingです » 5年前

To:usaoさま

分かりやすく、お優しい説明ありがとうございます!
目からウロコです。なるほど、forをそう使うんですね!
頑張って勉強します!
とりあえず、明日の午後に進めます。
本当にありがとうございました!!!

Math

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

#6

投稿記事 by Math » 5年前

エラーの出ない間違いがあります

=   は代入で 比較は ==

if 文{  }内の int 宣言は ローカルな新しい変数 ですよ!

コード:

#include <stdio.h>

int main(void)
{
	int a,b,c,d,e,f;
	a=1, b=1, c=1, d=1, e=1, f=1;
	
	printf(" 【線消しゲームへようこそ】\n\n");
	printf(" 選択 → 結果\n");
	printf("    a    %d\n", a);
	printf("   b c   %d %d\n", b,c);
	printf("  d e f  %d %d %d\n\n", d,e,f);
	
	printf("  さぁ、あなたの番ですよ!\n\n");
	printf("  コマンド?\n\n");
	
	printf("・aを消す→1を入力\n");
	printf("・bを消す→2を入力\n");
	printf("・cを消す→3を入力\n");
	printf("・dを消す→4を入力\n");
	printf("・eを消す→5を入力\n");
	printf("・fを消す→6を入力\n");
	printf("・bcを消す→23を入力\n");
	printf("・deを消す→45を入力\n");
	printf("・efを消す→56を入力\n");
	printf("・defを消す→456を入力\n\n\n");

	int user;

	scanf("%d", &user);
	printf("%d\n", user);
	if (user == 1) { a = 0;}	
	else if (user == 2) { b = 0;}
	else if (user == 3) { c = 0;}
	else if (user == 4) { d = 0;}
	else if (user == 5) { e = 0;}
	else if (user == 6) { f = 0;}
	
	printf(" 選択 → 結果\n");
	printf("    a    %d\n", a);
	printf("   b c   %d %d\n", b,c);
	printf("  d e f  %d %d %d\n\n", d,e,f);
	
	return 0;
}


アバター
usao
記事: 1887
登録日時: 11年前

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

#7

投稿記事 by usao » 5年前

とりあえずヒント.
「入力した範囲の棒が消されていき,全ての棒を消したら終了」という内容.

(うっかりC++だけど,Cとの差は,変数の定義が関数の先頭じゃないとか,bool使ってるくらいなので,まぁ問題にはならないだろう.)

コード:

//※includeは省略

//RowIndex段目の先頭index
//(前の投稿と一緒)
int StartIndexOfRow( int RowIndex )
{
    int ret = 0;
    for( int i=1; i<=RowIndex; ++i ){   ret += i;   }
    return ret;
}

//RowIndex段目に存在する棒の個数
int nBarOfRow( int RowIndex )
{   return RowIndex+1;  }

//全ての棒の状態をprintfで表示する
void PrintState( const int State[], int nRow )
{
    printf( "----------\n" );
    for( int iRow=0; iRow<nRow; ++iRow )
    {
        int StartIndex = StartIndexOfRow( iRow );
        int nBar = nBarOfRow( iRow );

        //前側余白
        int nSpace = nRow - iRow;
        for( int i=0; i<nSpace; ++i ){  printf( " " );  }

        for( int i=0; i<nBar; ++i )
        {
            printf( "%d ", State[ StartIndex+i ] );
        }
        printf( "\n" );
    }
    printf( "----------\n" );
}

//State[RangeStart]~State[RangeEnd]の範囲に,1つでも値が0の要素があるか? という判定
bool IsErasedBarExisting( const int State[], int RangeStart, int RangeEnd )
{
    for( int i=RangeStart; i<=RangeEnd; ++i )
    {
        if( State[ i ] == 0 )return true;
    }
    return false;
}

//終了判定
//※ここでは,てきとーに「全ての棒を消したら終了」という話になっている
bool Finished( const int State[], int nBar )
{
    for( int i=0; i<nBar; ++i )
    {
        if( State[i] != 0 )return false;
    }
    return true;
}

//main
int main(void)
{
    const int N_ROW = 3;    //段数
    const int N_BAR = 1+2+3;
    int State[N_BAR] = { 1,  1,1,  1,1,1 }; //棒の状態

    //初期状態表示
    PrintState( State, N_ROW );

    while( true )
    {
        //ユーザ入力.
        //改行を読み捨てる方法とかが,多分かなり不真面目.
        //なお,ユーザは全部自然数で値を入力する仕様(最上段の1本を消す場合は 1 1 1 と入力する).
        int TgtRow=0;
        int Left=0;
        int Right=0;

        printf( "何段目の 何本目から 何本目まで\n" );
        int nSucceeded = scanf( "%d %d %d", &TgtRow, &Left, &Right );
        getchar();  //※改行を読み捨てる
        if( nSucceeded != 3 )continue;

        //※中止用の特殊判定:全部0を指定したら中止するということにしてある.
        if( TgtRow==0 && Left==0 && Right==0 )
        {
            printf( "中止\n" );
            break;
        }

        //0-basedな値に変換
        --TgtRow;   --Left; --Right;

        //入力の範囲チェック
        if( TgtRow<0 || N_ROW<=TgtRow )continue;
        if( Left<0 || Right<Left )continue; 

        int nBar = nBarOfRow( TgtRow );
        if( nBar<=Left || nBar<=Right )continue;

        //指定範囲の棒を消す
        int StartIndex = StartIndexOfRow( TgtRow );
        int IndexOfLeft = StartIndex + Left;
        int IndexOfRight = StartIndex + Right;
        if( IsErasedBarExisting( State, IndexOfLeft, IndexOfRight ) )continue;  //チェック:指定範囲に既に消された棒がある場合は不正な入力

        for( int iBar=IndexOfLeft; iBar<=IndexOfRight; ++iBar )
        {   State[iBar] = 0;    }

        //状態表示
        PrintState( State, N_ROW );

        //終了判定
        if( Finished( State, N_BAR ) )
        {
            printf( "終了\n" );
            break;
        }
    }

    return 0;
}
テストしてみて気付いたけども,この入力の指定方法,わりと入力し難いw
「何本目まで消すか」じゃなくて「何本消すか」の方が入力が楽な気がする.

アバター
usao
記事: 1887
登録日時: 11年前

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

#8

投稿記事 by usao » 5年前

オフトピック
さくっとできるだろう,と思ったらscanfの改行がバッファに残る問題で無限ループして泣きそうになったでござる.
とりあえずgetchar()で強引に改行を抹殺したが……
まともな対処方法は,きっと詳しい誰かが書いてくれるだろう.

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

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

#9

投稿記事 by angel-wing » 5年前

To:Mathさん

ありがとうございます!
= を == にしたら、0になりました!面白いです!
でも、45 や 456 は無反応でした><。

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

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

#10

投稿記事 by angel-wing » 5年前

To:usaoさん

引き続きありがとうございます!
はっ、意外と難しいのですね……挫折が見えてきました(笑い)。
でも、皆さんのアドバイスのお陰で少し光明が見えてきました!


一応、仕様書ってほどじゃないですけど、こんな感じで考えていました!

■次の変数に1を表示させる
  [a]
   [c]
  [d][e][f]
   1
   11
  111

■選択した場所を0にする
※選択のルールは?
・一度に複数選ぶことも可能
・複数選ぶ場合は必ず横で選ぶ(縦や斜めはダメ)
・0を選択できない
・0をまたぐ場合の選択もできない
・自分の手番で1が1つしかない場合、敗北決定

■プログラムの手は既に決定させる(ランダムあり)

<program側の全パターンを決めておく>
・Aパターン:userがもし1なら、4か6をランダムで返す
 4(2or3→5+6、5or6→2+3、2+3 or 5+6→5or6 or 2or3)
 6(2or3→4+5、4or5→2+3、2+3 or 4+5→4or5 or 2or3)
・Bパターン:userがもし2か3なら、4・5か5・6をランダムで返す
・Cパターン:userがもし4か6なら、1を返す
・Dパターン:userがもし5なら、2・3を返す
・Eパターン:userがもし2・3なら、4・5・6か5を返す
・Fパターン:userがもし4・5か5・6なら、2か3をランダムで返す
・Gパターン:userがもし4・5・6なら、2・3を返す
 →一応、全通り、この後終了までの手筋を樹形図かする作業は完成しています。

アバター
usao
記事: 1887
登録日時: 11年前

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

#11

投稿記事 by usao » 5年前

棒が6本だから盤面の種類は 2の6乗(=64)パターンある.
終了状態(棒が1本だけ残っている状態)の6パターンを除外するとしても
program側の手番のときに有り得る盤面の種類は58パターン.
その全てに対応する出力を列挙したような実装を行うという話…なのかな?

まぁ方法はどうあれ,そこの部分が最も書くのが面倒な部分になりそうですし,
まずは,program側の思考処理はとりあえずの仮のもの
(例えば「現状で倒せる棒のうちのいずれか1本だけを倒すよ」みたいな簡単な処理)
を実装して,
「(programが弱いけれども)全体として正しく動く状態」の完成を目指すと良いのではないかと.
(それができたら,後はprogramの思考処理の中身を差し替えるだけ)

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

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

#12

投稿記事 by angel-wing » 5年前

To:usaoさん

ありがとうございます!
1本消す場合、2本消す場合、3本消す場合……中学生の確率っぽく場合分けして全パターンを潰していく予定です。
とりあえず後手必勝のパターンを作ってみて、少しずつ線を増やしながらprogramをランダム主体にしていこうかと
考えています。
まずは「動くprogram」ですね!
皆さんから頂いたアドバイスをいろいろ試してみます。
ありがとうございました(^^)ノ。

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

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

#13

投稿記事 by angel-wing » 5年前

分からないところが山積み大事件!!

(1) Userが、1~10以外を押してしまって「1~10を押してね」と叱られた後に、「さぁ、あなたの番ですよ!」に戻るところのループの書き方

(2) 一度「0」になった所を選択したら「そこは選べないよ!」と叱るif文の書き方(とりあえず、"0を押したら"で書きましたが"0になったら"に直したいのです)

(3) UserがEnterを押した後、programが手を選択する処理を始める動作("次はprogramの番です。Enterを押してください"の下へrand関数を繋げれば大丈夫なのかしら??)

(4) 乱数で2つから1つを1回だけ選択する場合の書き方(例えば、4(またはd)か6(またはf)のどちらか一方を選択するrand関数の書き方)

どうか、お暇な時で構いませんのでお助けくださいm(///)m

コード:

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

int main(void)
{
	int StickState[6] = { 1,  1,1,  1,1,1 };
	int a,b,c,d,e,f;
	a=1, b=1, c=1, d=1, e=1, f=1;
	
	printf(" 【線消しゲームへようこそ】\n\n");
	printf(" 選択 → 結果\n");
	printf("    a    %d\n", a);
	printf("   b c   %d %d\n", b,c);
	printf("  d e f  %d %d %d\n\n", d,e,f);
	
	printf("  さぁ、あなたの番ですよ!\n\n");
	printf("  コマンド?\n\n");
	srand ((unsigned int) time (NULL)); 
	printf("・aを消す→1を入力\n");
	printf("・bを消す→2を入力\n");
	printf("・cを消す→3を入力\n");
	printf("・dを消す→4を入力\n");
	printf("・eを消す→5を入力\n");
	printf("・fを消す→6を入力\n");
	printf("・bcを消す→7を入力\n");
	printf("・deを消す→8を入力\n");
	printf("・efを消す→9を入力\n");
	printf("・defを消す→10を入力\n\n\n");

	int user;

	scanf("%d", &user);
	if (user == 1) { a = 0;}
 	else if (user == 2) { b = 0;}
	else if (user == 3) { c = 0;}
	else if (user == 4) { d = 0;}
	else if (user == 5) { e = 0;}
	else if (user == 6) { f = 0;}
	else if (user == 7) { b = 0 , c = 0;}
	else if (user == 8) { d = 0 , e = 0;}
	else if (user == 9) { e = 0, f = 0;}
	else if (user == 10) { d = 0, e = 0, f = 0;}
	
	else if (user == 0) {printf(" そこは選べないでしょ!\n\n");}
	else { printf(" 1~10を押してね!\n\n") ;}
	
		printf(" 選択 → 結果\n");
	printf("    a    %d\n", a);
	printf("   b c   %d %d\n", b,c);
	printf("  d e f  %d %d %d\n\n", d,e,f);
	
	printf(" 次はprogramの番です。\n 落ち着いてEnterを押してください。\n\n");

	
	return 0;
}

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

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

#14

投稿記事 by angel-wing » 5年前

(5) 「6個のうち、0が5個になったら」または「6個のうち、1が1個になったら」プログラムを終了し、「あなたの負けです」と表示させるところの書き方ですが、
最初に定義したa~fを使い、【a+b+c+d+e+f >1の場合】までループし、【a+b+c+d+e+f =1になったらプログラム終了】とできないかな、と考えました。

ちなみに、1が残り一つになって終了した後に、
   printf(" もう一度→r あきらめる→q\n");  と表示させて、
int ch;
ch = getchar();
  
「rなら最初へ、qなら終了画面へ飛ばす」ようなことができれば良いなと思っています。
難しいですかね……。

アバター
usao
記事: 1887
登録日時: 11年前

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

#15

投稿記事 by usao » 5年前

まず,データの持ち方をちゃんと決めましょう.
6本の棒の状態を表すのに
元々やっていたように a,b,c,d,e,f なる6個の別々の変数を用いるのか,
それとも要素数6個の配列を用いるのか.
(少なくとも私の感覚では,前者の道は苦行そのものですが)

で,決めたならば,強い意志を持ってその決定に即したコードを書きましょう.

コード:

int StickState[6] = { 1,  1,1,  1,1,1 };
int a,b,c,d,e,f;
この2行のうち,どちらかは不要であるハズ.


・(1)(2)および(5):
 このあたりの事柄に関しては,私が#7で示したコードが参考になりませんか?
・(3)や(4)
 最初はとくかく必要最小限の要素に絞る(枝葉末節的な要素は問題ごと先送りにして,考えるべき/解決すべき 事柄の個数を減らす)方が良いのではないかと思います.
 ・(3):Enter入力させたいなら,まぁgetchar()とかでいいんじゃないでしょうか.
 ・(4):「C言語 乱数」とかでググると乱数の取得方法が見つかると思います.例えば1~100の範囲の乱数を得る手段を用意したならば「50以下か否か」みたいな判定で二択の処理は作れるのではないでしょうか.

アバター
usao
記事: 1887
登録日時: 11年前

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

#16

投稿記事 by usao » 5年前

で,それはそれとして,コードの現状を見るに,まずは処理構造の大枠側から徐々に作っていく方法を採ってみると良いのではないかと思ったりするのですが,どうでしょうか.

例)
少なくとも,「1ゲーム」分の処理というのは,勝敗が決まるまでループすることになるでしょうから,まずはそのループが存在する大枠を書く.
例えば以下のような.
細部の処理はとりあえず必要な事柄をコメントで書いておいて,後で埋める.

コード:

int main()
{
  //初期状態の準備
  (棒の状態を表す変数の値を全部1にしとくとかそういう事柄)
  ...

  //1ゲーム分の処理
  while( true )
  {
    //・プレイヤの入力処理
    //・入力に従ってデータを更新
    if( 終了状態なら ){ プレイヤの勝ち.break; }

    //・program側の思考処理
    //・思考結果に従ってデータを更新
    if( 終了状態なら ){ programの勝ち.break; }
  }
   ...
}
で,プレイヤの入力処理を考えた場合も,「まともな入力を行うまでは,入力をやり直させる」ならば,やはり処理構造としてはループでしょうから,例えばそこの部分にもう一個ループを書けば良いかもしれない.

コード:

...
//1ゲーム分の処理
  while( true )
  {
    //・プレイヤの入力処理
    while( true )  //入力を何度もやり直させるためのループ
    {
      //・scanfか何かで入力させる
      //・入力結果の妥当性をチェックする
      if( 入力結果が正当なら )break;
    }
    //・入力に従ってデータを更新
    ...
「終了時にもう1戦やるか終わるかを選びたい」という要件も,要は「『終わる』が選ばれるまでループする」のでしょうから,
例えば,上記のmain()の内容を全部 Game() という別の関数に移して,

コード:

void Game()
{
  //1ゲームの処理実装
}

//
int main()
{
  while( true )  //何戦もするためのループ
  {
     void Game();
    //・もう1戦やるか終わるかをscanfか何かで入力
    if( 「終わる」側が選ばれたら )break;
  }
  ...
}
とかすればできそうですよね.

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

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

#17

投稿記事 by angel-wing » 5年前

To:usao先生

わぁわぁわぁ、#7 の下まで見てませんでした;;
本当にありがとうございます!
結構難しいですが、がっつり研究してみます!!

かずま

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

#18

投稿記事 by かずま » 5年前

棒の状態をビットパターンで持ち、
コンピュータの次の手を全パターン持つようにしてみました。
ビット演算が苦手だと参考にならないかもしれません。

コード:

#include <stdio.h>   // fgets, printf, puts
#include <stdlib.h>  // atoi, rand

int pat[64][2] = {
	{ 0, 0 }, {  6, 6 }, {  5, 5 }, { 5, 6 }, { 4, 4 }, { 4, 6 }, { 4, 5 },
	{45,56 }, {  3, 3 }, {  3, 6 }, { 3, 5 }, {56,56 }, { 3, 4 }, { 3, 4 },
	{45,45 }, {456,456}, {  2, 2 }, { 2, 6 }, { 2, 5 }, {56,56 }, { 2, 4 },
	{ 2, 4 }, { 45,45 }, {456,456}, { 2, 3 }, { 2, 3 }, { 2, 3 }, { 2, 5 },
	{23,23 }, {  2, 3 }, {  2, 4 }, { 4, 5 }, { 1, 1 }, { 1, 6 }, { 1, 5 },
	{ 5, 6 }, {  1, 1 }, {  1, 4 }, {45,45 }, {456,5 }, { 1, 1 }, { 1, 3 },
	{ 1, 3 }, {  5, 6 }, {  1, 3 }, { 3, 1 }, { 4, 5 }, {45,56 }, { 1, 2 },
	{ 1, 2 }, {  1, 2 }, {  5, 6 }, { 1, 2 }, { 4, 6 }, { 4, 5 }, {45,56 },
	{23,23 }, {  2, 3 }, {  2, 3 }, { 1, 1 }, { 2, 3 }, {23,23 }, { 1, 1 },
	{ 1, 6 }
};

void print(int st)
{
	printf("\n" "    1        %d\n"
	            "   2 3      %d %d\n"
	            "  4 5 6    %d %d %d\n\n",
		st>>5&1, st>>4&1, st>>3&1, st>>2&1, st>>1&1, st&1);
}

int conv(int n)
{
	if (n >= 1 && n <= 6) return 1 << (6 - n);
	return n==23 ? 030 : n==45 ? 006 : n==56 ? 0003 : n==456 ? 007 : 0;
}

int main(void)
{
	char buf[256];
	int st = 077, n;

	print(st);
	while (1) {
		printf("あなたの番です。どれを消しますか? ");
		if (!fgets(buf, sizeof buf, stdin)) return 1;
		n = atoi(buf);
		if (n == 0) { puts("  入力間違いです"); continue; }
		n = conv(n);
		if (n == 0) { puts("  入力間違いです"); continue; }
		if ((st & n) != n) { puts("  0 は消せません"); continue; }
		st &= ~n;
		print(st);
		printf("コンピュータの番です。Enter を押してください");
		if (!fgets(buf, sizeof buf, stdin)) return 1;
		n = pat[st][rand() & 1];
		printf("\nコンピュータは %d を消します。\n", n);
		st &= ~conv(n);
		print(st);
		for (int i = 0; i < 6; i++)
			if (st == 1<<i) { puts("あなたの負けです。"); return 0; }
	}
}
実行例

コード:


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

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

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

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

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

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

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

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

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

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

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

あなたの負けです。

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

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

#19

投稿記事 by angel-wing » 5年前

To:かずま様

ご教授ありがとうございます!
こ、これは……天才……物凄いプログラムですね……。
意外と総量が少なくて、コンパクトにまとまっている気がします。

所々謎な文字が出てきますが、調べながらぜひ吟味させてください。

でもでも、 [苦しんで覚えるC言語]さんリンクの「学習用C言語開発環境 Ver 0.0.9.0」に
コピペして実行(コンパイル?)してみたところ、こんなエラーが出て実行できませんでした。

「53行目」で記述エラーを発見しました。
「identifier」を付け忘れています。

おこめ: 「オブジェクト参照がオブジェクトインスタンスに設定されていません」←これが原因かもしれない \(_)

このアプリ?はちょっと信用できないところがありまして(笑)、他で起きているエラーを
どこかテキトーところ(正常)のせいにしたりします。ちょっと謎々しいですが、調査してみます。

本当にありがとうございました!!

アバター
usao
記事: 1887
登録日時: 11年前

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

#20

投稿記事 by usao » 5年前

オフトピック
プレイヤ側の敗北判定しか用意されていない突き詰められ具合が素敵すぎる

かずま

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

#21

投稿記事 by かずま » 5年前

angel-wing さんが書きました:
5年前
でもでも、 [苦しんで覚えるC言語]さんリンクの「学習用C言語開発環境 Ver 0.0.9.0」に
コピペして実行(コンパイル?)してみたところ、こんなエラーが出て実行できませんでした。
for文で変数を宣言できないんですね。
次のようにしてください。

コード:

	int i;
	for (i = 0; i < 6; i++)

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

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

#22

投稿記事 by angel-wing » 5年前

usao さんが書きました:
5年前
オフトピック
プレイヤ側の敗北判定しか用意されていない突き詰められ具合が素敵すぎる
おはようございます!
名付けて「お前はもうツんでいる」プログラムですね……。

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

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

#23

投稿記事 by angel-wing » 5年前

かずま さんが書きました:
5年前
angel-wing さんが書きました:
5年前
でもでも、 [苦しんで覚えるC言語]さんリンクの「学習用C言語開発環境 Ver 0.0.9.0」に
コピペして実行(コンパイル?)してみたところ、こんなエラーが出て実行できませんでした。
for文で変数を宣言できないんですね。
次のようにしてください。

コード:

	int i;
	for (i = 0; i < 6; i++)
おはようございます!
治りました、動きました、負けまくりで楽しいです!
ありがとうございます!!
ビット演算は初耳学なので、研究に入りますノ

かずま

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

#24

投稿記事 by かずま » 5年前

バグがありました。
pat[31] が { 4, 5 } となっていますが、これは { 4, 6 } です。
他にも間違いがあるかもしれません。

ビット演算を使わないで配列を使うように書き換えてみました。

コード:

#include <stdio.h>   // fgets, printf, puts
#include <stdlib.h>  // atoi, rand

int pat[64][2] = {
	{ 0, 0 }, {  6, 6 }, {  5, 5 }, { 5, 6 }, { 4, 4 }, { 4, 6 }, { 4, 5 },
	{45,56 }, {  3, 3 }, {  3, 6 }, { 3, 5 }, {56,56 }, { 3, 4 }, { 3, 4 },
	{45,45 }, {456,456}, {  2, 2 }, { 2, 6 }, { 2, 5 }, {56,56 }, { 2, 4 },
	{ 2, 4 }, { 45,45 }, {456,456}, { 2, 3 }, { 2, 3 }, { 2, 3 }, { 2, 5 },
	{23,23 }, {  2, 3 }, {  2, 4 }, { 4, 6 }, { 1, 1 }, { 1, 6 }, { 1, 5 },
	{ 5, 6 }, {  1, 1 }, {  1, 4 }, {45,45 }, {456,5 }, { 1, 1 }, { 1, 3 },
	{ 1, 3 }, {  5, 6 }, {  1, 3 }, { 3, 1 }, { 4, 5 }, {45,56 }, { 1, 2 },
	{ 1, 2 }, {  1, 2 }, {  5, 6 }, { 1, 2 }, { 4, 6 }, { 4, 5 }, {45,56 },
	{23,23 }, {  2, 3 }, {  2, 3 }, { 1, 1 }, { 2, 3 }, {23,23 }, { 1, 1 },
	{ 1, 6 }
};

void print(int *st)
{
	printf("\n" "    1        %d\n"
	            "   2 3      %d %d\n"
	            "  4 5 6    %d %d %d\n\n",
		st[0], 	st[1], 	st[2], 	st[3], 	st[4], 	st[5]);
}

int conv(int n, int *user)
{
	if (n >= 1 && n <= 6) return user[n-1] = 1;
	if (n == 23)  return user[1] = user[2] = 1;
	if (n == 45)  return user[3] = user[4] = 1;
	if (n == 56)  return user[4] = user[5] = 1;
	if (n == 456) return user[3] = user[4] = user[5] = 1;
	return 0;
}

int game(void)
{
	char buf[256];
	int st[6] = { 1, 1,1, 1,1,1 }, i;

	print(st);
	while (1) {
		printf("あなたの番です。どれを消しますか? ");
		if (!fgets(buf, sizeof buf, stdin)) return 1;
		i = atoi(buf);
		if (i == 0) { puts("  入力間違いです"); continue; }
		int user[6] = { 0 };
		if (conv(i, user) == 0) { puts("  入力間違いです"); continue; }
		for (i = 0; i < 6; i++)
			if (user[i] == 1) {
				if (st[i] == 0) { puts("  0 は消せません"); continue; }
				st[i] = 0;
			}
		print(st);
		printf("コンピュータの番です。Enter を押してください");
		if (!fgets(buf, sizeof buf, stdin)) return 1;
		i = st[0]*32 + st[1]*16 + st[2]*8 + st[3]*4 + st[4]*2 + st[5];
 		i = pat[i][rand() % 2];
		printf("\nコンピュータは %d を消します。\n", i);
		int comp[6] = { 0 };
		conv(i, comp);
		for (i = 0; i < 6; i++)
			if (comp[i] == 1) st[i] = 0;
		print(st);
		if (st[0]+st[1]+st[2]+st[3]+st[4]+st[5] == 1) {
			puts("あなたの負けです。\n"); return 0;
		}
	}
}

int main(void)
{
	while (game() == 0) {
		char buf[256];
		printf("もう一度やりますか(r)? ");
		if (!fgets(buf, sizeof buf, stdin) || buf[0] != 'r') break;
	}
}

かずま

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

#25

投稿記事 by かずま » 5年前

かずま さんが書きました:
5年前
ビット演算を使わないで配列を使うように書き換えてみました。

コード:

		for (i = 0; i < 6; i++)
			if (user[i] == 1) {
				if (st[i] == 0) { puts("  0 は消せません"); continue; }
				st[i] = 0;
			}
これでは 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; }

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

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

#26

投稿記事 by angel-wing » 5年前

To:かずま様

・ビット演算の方の訂正
・ビット演算を使わないプログラム
どちらもありがとうございます!!

新しい方、分岐は複雑ですが見たことのある文字が多いので頑張れば理解できそうです。
一生懸命研究してみます。ありがとうございました!!

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

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

#27

投稿記事 by angel-wing » 5年前

To:かずまさま

おや、おやおや?
「70行目で宣言を書き忘れています」などという、謎エラーが出て実行できません;;

}
}
}     ←70行目って、これです!笑
int main(void)

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

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

#28

投稿記事 by angel-wing » 5年前

↑すみません↑
私の勘違いでした!!!!!
} を1個消し忘れていました><
問題なく起動しました、ありがとうございます!!!

かずま

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

#29

投稿記事 by かずま » 5年前

かずま さんが書きました:
5年前
ビット演算が苦手だと参考にならないかもしれません。

コード:

		for (int i = 0; i < 6; i++)
			if (st == 1<<i) { puts("あなたの負けです。"); return 0; }
棒が 1本だけ残っているかどうかの判定ですが、
ビット演算ならループにしなくても済みます。

コード:

		if ((st-1 & st) == 0) { puts("あなたの負けです。"); return 0; }

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

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

#30

投稿記事 by angel-wing » 5年前

調べましたが、分からないところがたくさんです;;
お助けいただければ幸いです<(..)>。

(1) return 0と1が使い分けられていますが、何か意図があるのでしょうか(全て0にしてみてもエラーは出ない様子ですが)。

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

(3) int game内の if (!fgets(buf, sizeof buf, stdin)) return 0;
 には、どのような意味がありますか。「stdinでキーボードから文字列を受け取り、関数冒頭で定義したbufに当てはめてサイズを取得する→もしサイズが取得できなければ、printfの内容を表示する」という解釈で大丈夫でしょうか……。

(4) int user以下で、if (conv(i, user) == 0) { puts("エラー"); continue; }
 ですが、「userの入力が0ならば、エラー表示をする」ように読めたのですが、実際は消し方が違う場合(例えば12のように縦に消したり)に出るエラーだと思います。このプログラムの0はどのような意味なのでしょうか。

(5) (4)の続き、if (user == 1) if (st == 0) i = 9; else st = 0;
 ここの3行ですが、「userが入力した配列が1の場合、または配列の中が0になった場合かiが9の場合、もしくは配列内が0の場合、iが5になるまで繰り返す(for)」と読みましたが、違いますよね……どのように解釈すればよいのでしょうか。

(6) i = st[0]*32 + st[1]*16 + st[2]*8 + st[3]*4 + st[4]*2 + st[5];
 ここがプログラムの中枢計算だと思うのですが、これはおそらく「64パターンを2進数に分解して和を求めている」ということでしょうか。
 すると、次のi = pat[rand() % 2]; は、「和が偶数の場合」の処理ということでしょうか。これは凄いですね……。

(7) int mainの、while (game() == 0) {
 というのは、「game関数が0の場合→ゲームが終わった場合」という意味でしょうか。

(8) 最後の、if (!fgets(buf, sizeof buf, stdin) || buf[0] != 'r') break;
 というのは、「文字列がサイズを持たない場合(勝負がついた場合?) or 'r’が入力されない場合は、ゲームを終了する(main関数を抜ける)」という意味でしょうか。

↓少し弄ってあります。。。


コード:

 
#include <stdio.h>   // fgets, printf, puts
#include <stdlib.h>  // atoi, rand

int pat[64][2] = {
	{ 0, 0 }, {  6, 6 }, {  5, 5 }, { 5, 6 }, { 4, 4 }, { 4, 6 }, { 4, 5 },
	{45,56 }, {  3, 3 }, {  3, 6 }, { 3, 5 }, {56,56 }, { 3, 4 }, { 3, 4 },
	{45,45 }, {456,456}, {  2, 2 }, { 2, 6 }, { 2, 5 }, {56,56 }, { 2, 4 },     //行数・要素数を64パターン
	{ 2, 4 }, { 45,45 }, {456,456}, { 2, 3 }, { 2, 3 }, { 2, 3 }, { 2, 5 },     // 2の6乗が64だ
	{23,23 }, {  2, 3 }, {  2, 4 }, { 4, 6 }, { 1, 1 }, { 1, 6 }, { 1, 5 },     //このパターンが示す意味や順序は謎すぎる→ビット関数?
	{ 5, 6 }, {  1, 1 }, {  1, 4 }, {45,45 }, {456,5 }, { 1, 1 }, { 1, 3 },
	{ 1, 3 }, {  5, 6 }, {  1, 3 }, { 3, 1 }, { 4, 5 }, {45,56 }, { 1, 2 },
	{ 1, 2 }, {  1, 2 }, {  5, 6 }, { 1, 2 }, { 4, 6 }, { 4, 5 }, {45,56 },
	{23,23 }, {  2, 3 }, {  2, 3 }, { 1, 1 }, { 2, 3 }, {23,23 }, { 1, 1 },
	{ 1, 6 }
};

void print(int *st)
{
	printf("\n" " ~*~*~*~*~*~*~*~*~*~*~\n"          //表示させるだけで戻り値を要求しないからvoid関数
	            "     1        %d\n"                            //*はポインタを表す間接演算子で、付着したものはポインタ変数となる
	            "    2 3      %d %d\n"                          //1~6を、配列[0]~[5]と置いてポインタ設置(この順序)
	            "   4 5 6    %d %d %d\n\n",                     //ポインタ関数にしたので、ポインタに代入可能になった様子
		st[0], 	st[1], 	st[2], 	st[3], 	st[4], 	st[5]);
}

int conv(int n, int *user)                                     //まずは、初期設定をする
{                                                              //convの関数で、数字と各配列を変換する
	if (n >= 1 && n <= 6) return user[n-1] = 1;                //1つ変換する場合、nが1~6の場合は、n-1をした箇所の配列を1に変換
	if (n == 23)  return user[1] = user[2] = 1;                
	if (n == 45)  return user[3] = user[4] = 1;                //2つ変換する場合、nが23→1,2を、45→3,4を、56→4,5を1に変換
	if (n == 56)  return user[4] = user[5] = 1;
	if (n == 456) return user[3] = user[4] = user[5] = 1;      //3つ選択する場合、nが456→3,4,5の配列を1に変換
	return 0;
}

int game(void)                                                 //ここからがゲームのメインとなる関数!
{
	char buf[256];                                             //文字入力用の変数bufを宣言
	int st[6] = { 1, 1,1, 1,1,1 }, i;                          //配列を3段で認識させ、数値型iも宣言
	print(st);                                                 //三段に認識された配列を表示(上で1を代入されたものが出てくる)
	while (1) {                                                //while文によってループを開始する
		printf("【あなたの番だよ。どれを消すの?】\n");        //fgets(file get)はファイルから文字列を1行取得する関数
		if (!fgets(buf, sizeof buf, stdin)) return 0;          //stdinは標準入力(キーボード)から文字列を受け取る標準ストリームだけど、記述内

容が謎
		i = atoi(buf);                                         //atoiで読み取った文字列をint型の数字に変換する
		if (i == 0) { puts("\n<エラーパターンAを確認!> そこはダメ! ふざけてないで真剣にやって!\n"); continue; }  //putsはprintfでも可、continue

はwhileブロック内をループ
		int user[6] = { 0 };                                   //userの配列を全て0で初期化して宣言
		if (conv(i, user) == 0) { puts("\n<エラーパターンBを確認!> どういう消し方だよ……ねぇ、やる気あるの?\n"); continue; } //前半が謎すぎる
		for (i = 0; i < 6; i++)                                //変数iを、0~5まで増やしていく
			if (user[i] == 1)                                  //これ以降3行のfor文が謎すぎて頭髪が抜けた
				if (st[i] == 0) i = 9;                         //i=9って何だろう?
				else st[i] = 0;                                //st[i]=0が分からなくて、また頭髪が抜けた
		if (i > 6) { puts("\n<エラーパターンCを確認!> ねぇ知ってる? 0は消せないんだよ?\n"); continue; } //どうしてi>6 で0を表すのかが謎!
		print(st);                                             //printfじゃなくて、print??
		printf("今度は私が消すから「Enter」を押してね!");     //putsにしても大丈夫だった件
		if (!fgets(buf, sizeof buf, stdin)) return 0;          //returnは1になっていたが、0でも問題がなかった件
		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];                                //上記の和を2で割る →おそらく、和が偶数になるようにランダムで選択している?
		printf("\n私は %d を消したよ。\n", i);                 //その解をprogramの手とする
		int comp[6] = { 0 };                                   //userと同じくprogramの配列を全て0で初期化している
		conv(i, comp);                                         //変数iとcomp(program)を宣言
		for (i = 0; i < 6; i++)                                //変数iを0から5に増やしていく
			if (comp[i] == 1) st[i] = 0;                       //配列が1になった場合、0を代入? ifを復習し直し!
		print(st);                                             //printfじゃなくて、またprint?
		if (st[0]+st[1]+st[2]+st[3]+st[4]+st[5] == 1) {        //配列の、恐らく論理和が1ということは、userの手番で1を引くから勝負あり
			puts("(^.^)v† はい、あなたの負け~!!"); return 0; //上記の結果により、putsでuser敗北の記述
		}
	}
}

int main(void)                                                      //最後にmain関数ですと!?
{
		puts("線消しGAME『お前はもうツんでいる』へようこそ☆彡");   //main関数が先処理なので、ゲーム開始文をここに入れてみた件
	while (game() == 0) {                                           //game関数が0の場合→gameが終わった場合?
		char buf[256];                                              //文字入力用のbufを変数宣言
		puts("時間の無駄だけど、もう一度やる→r\n実力不足を認めて、潔く立ち去る→他\n");  //printfでも可能
		if (!fgets(buf, sizeof buf, stdin) || buf[0] != 'r') break; //main関数を抜ける条件が2つ→[43行目と同様] or [r以外を押すこと]かな?
	}                                                               //[!fgets(buf, sizeof buf, stdin)の内容は、勝負がついた]場合
}

 

アバター
usao
記事: 1887
登録日時: 11年前

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言語何でも質問掲示板” へ戻る