人間とコンピュータが交互に石を取り除く

説明の画像

人間とコンピュータが交互に石を取り除きます。

前田稔(Maeda Minoru)の超初心者のプログラム入門

プロジェクトの設定

  1. テンプレートまたは、空のプロジェクトを作成して下さい。
    作成されたフォルダーに石の画像ファイル(Jewel.bmp)を格納して下さい。
  2. 「次のゲーム」を開始するメニューを設定します。
    ID キャプション
    IDM_RESET 次のゲーム(&S)
  3. 乱数を初期化する Time() 関数を使用するので Stdafx.h ファイルで time.h を取り込んで下さい。
        // TODO: プログラムで必要なヘッダー参照を追加してください。
        #include    <time.h>
        
  4. #define とグローバル領域の定義です。
    Timer を使うので ID を定義して下さい。
    グローバル領域にゲームで使用する領域を定義します。
    Count は山に積まれた石の数で、Max は一度に取り除くことができる個数です。
    Teban で人間とコンピュータの手番を識別します。
    pt はマウスがクリックされた座標を格納する領域です。
    Win, Lose でゲームの勝敗を記録します。
    rt1, rt2 はメッセージを表示する矩形領域で msg が rt1 に表示するメッセージです。
        #define ID_TIMER    (WM_APP + 0)
                :
        // グローバル変数:
                :
        int         Count;                  // 山に積まれた石の数
        int         Max;                    // 一度に取り除く最大数
        int         Teban;                  // 手番(0:Comp, 1:人間)
        POINT       pt;                     // クリックされた座標
        int         Win,Lose;               // 勝敗
        RECT        rt1 = { 50,30,400,100 };
        RECT        rt2 = { 50,340,400,400 };
        LPSTR       msg=
        { "山の石をあなたと私が交互に取り除きます。\n最後の石を取らされた方が負けです。\n" };
        char        str[256],wk[80];
        
  5. WndProc() に WM_CREATE: を追加して、アプリケーションの初期化を行います。
    勝敗の記録をクリアして乱数を初期化します。
    Start() 関数で次のゲームを開始する準備処理を行います。
        time_t  nowtime;
        int     work;
    
        switch( message ) 
        {   case WM_CREATE:
                Win=Lose= 0;
                time(&nowtime);
                srand(nowtime);
                Start();
                break;
        
  6. マウスがクリックされたときの処理を追加します。
    Teban != 1 のときは、人間がプレイする手番ではありません。
    POINT 構造体にマウスの座標を格納して、座標から石の数を計算します。
    work に取り除く石の数を計算して、プレイが正しいかを調べます。
    Count に残った石の数を設定して InvalidateRect() で描画します。
    人間のプレイが終わるとコンピュータの手番に設定してタイマをセットします。
    コンピュータのプレイはタイマ割り込みで行います。
        case WM_LBUTTONDOWN:
            if (Teban!=1)   break;
            pt.x= マウスの座標を格納して下さい
            pt.y= マウスの座標を格納して下さい
            work= 取り除く石の数を格納して下さい
            if (work<=0 || work>Max)
            {   wsprintf(str,"クリックの位置が不正です(最大 %d)",Max);
                MessageBox(hWnd,str,"Error Message",MB_OK);
                break;
            }
            Count-= 残った石の数を格納して下さい
            Teban= 0;
            InvalidateRect(hWnd,NULL,TRUE);
            UpdateWindow(hWnd);
            SetTimer(hWnd, ID_TIMER, 600, NULL);
            break;
        
  7. コンピュータのプレイです。
    このプログラムでは石を一個だけ取り除いています。
    コンピュータが勝てるようにプログラムを修正して下さい。
    あまりコンピュータが強すぎると面白くないので20%の確立でわざと間違えましょう。
        case WM_TIMER:
            KillTimer(hWnd, ID_TIMER);
            if (Teban!=0)   break;
            Count--;
            Teban= 1;
            InvalidateRect(hWnd,NULL,TRUE);
            UpdateWindow(hWnd);
            break;
        
  8. メニューから「次のゲーム」が選択されたときの処理です。
    Start() 関数を実行してウインドウの再描画を行います。
        switch( wmId ) 
        {   case IDM_RESET:
                Start();
                InvalidateRect(hWnd,NULL,TRUE);
                break;
        
  9. WM_PAINT にウインドウを描画するコードを記述します。
    ここでは View() 関数を呼ぶだけです。
        case WM_PAINT:
            hdc = BeginPaint (hWnd, &ps);
            // TODO: この位置に描画用のコードを追加してください...
            View(hdc,"jewel.bmp",Count);
            EndPaint( hWnd, &ps );
            break;
        
  10. 次のゲームを設定する Start() 関数です。
    山の石と取り除く数を設定して、人間の先手で開始します。
        void  Start()
        {   Count= rand()%20+11;
            Max  = rand()%5+2;
            Teban= 1;
        }
        
  11. 簡単なメッセージと山の石を表示する View 関数です。
    File にファイル名が設定されていないときは直ちに終了します。
    n 個の石の画像を一行に十個ずつ並べて表示します。
    表示が終わるとゲームの終了を判定する Hantei() を呼びます。
        HRESULT View(HDC hdc, char File[], int n)
        {   HBITMAP         hbm;
            HDC             hmdc;
            BITMAP          bm;
            int             i,xp,yp;
    
            if (File[0]=='\0')  return FALSE;
            strcpy(str,msg);
            wsprintf(wk,"最大 %d 個まで取り除くことができます。",Max);
            strcat(str,wk);
            //DrawText() で wk を表示して下さい
            wsprintf(str,"現在の勝敗は\nわたしの %d 勝で\nあなたの %d 勝です。",Win,Lose);
            //DrawText() で str を表示して下さい
    
            //File[] から画像ファイルをロードして下さい
                    :
            for(i=0; i<n; i++)
            {   xp= i%10;
                yp= i/10;
                //座標(xp*幅,yp*高さ) に石を表示します
            }
            //取得したオブジェクトを開放します
            return TRUE;
        }
        
  12. 勝敗判定を判定する関数です。
    山の石が一個以下になるとゲームの終了です。
    最後の石を取らされた方が負けで、勝敗を調べて勝ち負けをカウントします。
        void Hantei(HDC hdc)
        {
            if (Count>1)    return;
            if ((Count==0 && Teban==0) || (Count==1 && Teban==1))
            {   SetTextColor(hdc,RGB(255,0,0));
                TextOut(hdc,50,200,"わたしの勝ちです", 16);
                Win++;
            }
            else
            {   SetTextColor(hdc,RGB(0,255,0));
                TextOut(hdc,50,200,"あなたの勝ちです", 16);
                Lose++;
            }
            SetTextColor(hdc,RGB(0,0,0));
        }
        

【演習】

  1. プログラムを完成させて下さい。

【石取りゲームの必勝法】

  1. 今仮に「最大3個」までの石を取り除くことができるものとします。
    石を5個(3+2) 個にして相手に手を渡せば勝てることを確認して下さい。
  2. 石を5個にして相手に手を渡すには、石を9個に設定すれば良いですね。
    相手が1~3個のどの手を打っても、5個にして手を渡すことができるでしょう。
  3. この関係を整理すると、取り除く最大数を n とすると「(n+1) の倍数+1個」の数に設定して相手に手を渡せば必勝です。
    相手が必勝のパターンで迫ってきたときは、仕方がないので乱数で石数を決めます。
    また、あまりコンピュータが強すぎると面白くないので20%の確立でわざと間違えましょう。

[Previous Chapter ↑] マウスのクリックで石を取り除く

超初心者のプログラム入門(Win32API C++)