三山くずし Game Program

Z80で三山くずしゲームを作ってから今回で何度目になるのでしょう。(^◇^;)
最新の DirectX9 を使いオブジェクト指向のプログラム構成で Modeless Dialog Box を使用してリニューアルしました。
石の画像やバックミュージック(MIDI)や効果音(WAV)を組み合わせて、三山くずしも随分進歩したものです。
今回はさらに人間同士がインターネットで対戦するネットゲームにも挑戦します。 \(^o^)/
このプログラムの開発には、私が作成した Miyama Object Class を使っています。

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

三山くずしゲームのルール

  1. 三個の山にそれぞれ「1~15」の石を置きます。
  2. 石の数は、コンピュータが乱数を使って決めます。
  3. 二人のプレーヤー(人間とコンピュータ)が交互に石を取り除きます。
  4. 一つの山からであれば好きなだけ石を取り除くことができます。
  5. 最後の石を取らされた方が負けです。


三山くずしゲームのルールには「最後の石を取った方が勝ち」と「最後の石を取らされた方が負け」がありますが、 今回は少し複雑な「最後の石を取らされた方が負け」を採用します。

三山くずしゲームのルールは簡単ですが、勝ち方を知らないとなかなか勝てません。
私の友達などはどうしても勝てず、舌足らずな「私の勝ちです」の声に怒りを覚えているほどです。(^_^;
この声は私の下の娘が幼稚園に行く前に録音したもので、今となっては二度と手に入らない貴重な財産です。(^_^;

次の二種類の三山くずしゲームプログラムを作成します。
  1. コンピュータと人間が対戦するゲームです。
  2. 人間同士がインターネットで対戦するゲームです。
コンピュータと人間が対戦するゲームではプログラムを起動すると自動的にデモ・プレイが始まります。
このゲームは「圧倒的に先手が有利」なので「開始 Button をクリック」すると人間側の先手でゲームが始まります。
間違ってプレイしたときや、数手前に戻ってプレイを再開したいときは ScrollBar の操作で、何手でも戻すことができます。

ネット対戦ゲームも HOST としてプログラムを立ち上げるとデモ・プレイが始まり、接続待ちの状態になります。
Client 側が HOST に接続するとデモ・プレイが終わり、ネット対戦ゲームが始まります。
先手が有利なので Client 先手でゲームを始めます。

C# でも Object Class の基礎から「三山くずしゲーム」を作成しています。
石の画像を描画 をクリックして下さい。
Java Applet でもホームページから起動して楽しめるゲームを作成しています。
残念ながら現在のサーバーでは Java Applet を起動できないので悪しからず。
三山くずしゲーム をクリックして下さい。

Miyama Object Class のヘッダーファイル Miyama.h です。
//★ Miyama Object Class Header   2003-04-26  前田 稔
#include    <dmusici.h>
#define     ERRMSG(x)  MessageBox(NULL,x,"Audio",MB_OK);

class Miyama
{ //★非公開部分
  protected:
    HDC         hMdc;               //画像のコンパティブルDC
    long        Width,Height;       //画像サイズ
    BYTE        LG[45][3];          //ロギングテーブル
    HFONT       hFont,hFontOld;

    void        LoadBMP(HWND, LPCSTR, long *Width, long *Height);
    void        Settbl();
    int         ItemNo(POINT *pt);
    void        Stone(HDC hdc, RECT *rt, int st, int ed, int typ);
    HFONT       SetMyFont(HDC hdc,LPCTSTR face,int h,int angle);

  //★公開部分
  public:
    BYTE        T[3];               //三山 Table
    HWND        hParent;            //Windows Handle
    HWND        hDlg;               //DialogBox Handle
    WORD        yama,down;          //コンピュータが取り除く山と石
    WORD        CNT;                //記録数
    WORD        Lose_c, Win_c;

    Miyama(HWND, LPSTR);            //Constructor
    ~Miyama();                      //Destructor
    void        Start();            //Game Start
    void        Disp(HDC);
    int         Stone();            //山の石をカウント
    int         ManPlay(POINT *pt); //Player がプレイ
    int         Play();             //Computer Play
    void        DemoPlay();         //Demo Play
    int         CntDown();          //一個ずつ取り除く
    WORD        Log(WORD);          //棋譜の記録
    WORD        Log() { return(Log(CNT)); }
    void        Saigen(short);      //棋譜の再現
};

class Audio
{ protected:
    IDirectMusicLoader8*      g_pLoader;
    IDirectMusicPerformance8* g_pPerformance;
    IDirectMusicPerformance8* g_pPerformance2;
    IDirectMusicSegment8*     g_pSegment;

    WCHAR       AudioFile[MAX_PATH];
    HRESULT     InitializeSynth();
    HRESULT     LoadSegment(IDirectMusicSegment8** pSegment);
    HRESULT     LoadRes(HMODULE hMod, WORD ResourceID,
                        IDirectMusicSegment8** pSegment);
    HRESULT     InitDirectAudio();
    HRESULT     FreeDirectAudio();

  public:
    Audio();                    //Constructor
    ~Audio();                   //Destructor
    void        Play(int rep,IDirectMusicSegment8* pSegment,
                             IDirectMusicPerformance8* pPerform);
    void        Play(int rep= 0)  { Play(rep,g_pSegment,g_pPerformance); }
    void        Play2(int rep= 0) { Play(rep,g_pSegment,g_pPerformance2); }
    void        Stop(IDirectMusicPerformance8* pPerform);
    void        Stop()  { Stop(g_pPerformance); }
    void        Stop2() { Stop(g_pPerformance2); }
    void        SetVol(IDirectMusicPerformance8* pPerform, long *nVol);
    void        SetVol(long *nVol)  { SetVol(g_pPerformance, nVol);  }
    void        SetVol2(long *nVol) { SetVol(g_pPerformance2, nVol); }
    HRESULT     Load(LPSTR Audio,IDirectMusicSegment8** pSegment,
                                 IDirectMusicPerformance8* pPerform);
    HRESULT     Load(LPSTR Audio)
                { return(Load(Audio,&g_pSegment,g_pPerformance)); }
    HRESULT     Load2(LPSTR Audio)
                { return(Load(Audio,&g_pSegment,g_pPerformance2)); }
    HRESULT     Load(HMODULE hMod, WORD ResourceID,
                     IDirectMusicSegment8** pSegment,
                     IDirectMusicPerformance8* pPerform);
    HRESULT     Load(HMODULE hMod, WORD ResourceID)
                { return(Load(hMod,ResourceID,&g_pSegment,g_pPerformance)); }
    HRESULT     Load2(HMODULE hMod, WORD ResourceID)
                { return(Load(hMod,ResourceID,&g_pSegment,g_pPerformance2)); }
};

Miyama Object Class のプログラムファイル Miyama.cpp です。
/**********************************************************/
/*★ Miyama1 Object Class の定義   2003-03-15  前田 稔 ★*/
/**********************************************************/
#include    <windows.h>
#include    <time.h>
#include    "Miyama.h"

//山の番号と数の領域と全体の領域
RECT    BoxA= {50,300,600,480};
RECT    BoxB= {50,320,600,480};
//表示領域全体
RECT    INV_ALL= {30,60,600,480};
//石と文字の更新領域
RECT    INV_R[3]= {{30,60,180,480},{230,60,380,480},{430,60,580,480}};
//石の表示領域
RECT    Box[3]= {{30,60,180,284},{230,60,380,284},{430,60,580,284}};

char    msg_1[]=
{ "一の山                   二の山                    三の山"};

// Constructor
Miyama::Miyama(HWND hwnd, LPSTR szBitmap)
{   time_t  nowtime;
    time(&nowtime);
    srand(nowtime);
    hParent= hwnd;
    Lose_c=Win_c= 0;
    Settbl();
    LoadBMP(hParent,szBitmap,&Width,&Height);
}

// Destructor
Miyama::~Miyama()
{   DeleteDC(hMdc);
}

// 三個の山に石を設定
void  Miyama::Start()
{
    Settbl();
    CNT= Log(0);
    down= 0;
    InvalidateRect(hParent,NULL,TRUE);
    UpdateWindow(hParent);
}

// 三個の山を表示
void  Miyama::Disp(HDC hdc)
{   char        w[80];
    int         i;

    hFont= SetMyFont(hdc, (LPCTSTR)"MS 明朝", 16, 0);
    hFontOld = (HFONT)SelectObject(hdc, hFont);
    SetTextColor(hdc,RGB(0,0,255));
    SetBkColor(hdc,RGB(255,255,255));
    //3個の山を表示
    for(i=0; i<3; i++)
        Stone(hdc,&Box[i],0,T[i],0);
    DrawText(hdc,msg_1,-1,&BoxA,DT_WORDBREAK);
    wsprintf(w,"  %2d %24d %26d",T[0],T[1],T[2]);
    DrawText(hdc,w,-1,&BoxB,DT_WORDBREAK);
    SelectObject(hdc, hFontOld);
    DeleteObject(hFont);
    return;
}

// 石を表示する
void  Miyama::Stone(HDC hdc, RECT *rt, int st, int ed, int typ)
{   int    i,x,y;
    for(i=st; i<ed; i++)
    {   x= rt->left+(i/5)*Width;
        y= rt->top+(4-(i%5))*Height;
        StretchBlt(hdc,x,y,(int)(Width/1.5),(int)(Height/1.5),
                   hMdc,typ*Width,0,(int)Width,(int)Height,SRCCOPY);
    }
}

// Player がプレイする
int   Miyama::ManPlay(POINT *pt)
{
    if (ItemNo(pt)==0)    return(0);
    if (T[pt->x]<=pt->y)  return(0);
    T[pt->x]= (BYTE)pt->y;
    InvalidateRect(hParent,&INV_R[pt->x],TRUE);
    UpdateWindow(hParent);
    return(1);
}

// Demo Play
void  Miyama::DemoPlay()
{   int     n1;         //石が残っている山の数
    int     n2;         //石が一個だけの山の数
    int     w;          //石が一番多い山の番号
    int     i,n;

    i= 3;
    if (rand()%5)
    {   for(i=n1=n2=0,w=0; i<3; i++)
        {   if (T[i])
            {   n1++;
                if (T[i]==1)    n2++;
                if (T[i]>T[w])  w= i;
            }
        }
        switch(n1)
        {   case 1: //石が残っている山は一個
                if (n2==1)   break;
                T[w]= 1;
                InvalidateRect(hParent,NULL,TRUE);
                UpdateWindow(hParent);
                return;
            case 2: //石が残っている山は二個
                if (n2==0)  break;
                T[w]= 0;
                InvalidateRect(hParent,NULL,TRUE);
                UpdateWindow(hParent);
                return;
            case 3: //石が残っている山は三個 
                if (n2!=2)  break;
                T[w]= 1;
                InvalidateRect(hParent,NULL,TRUE);
                UpdateWindow(hParent);
                return;
        }
        w= T[0]^T[1]^T[2];
        for(i=0; i<3; i++)
        {   n=T[i]^w;
            if (n<T[i]) break;
        }
    }
    if (i>2)
    {   while(T[i=rand()%3]==0);
        n=rand()%T[i];
    }
    T[i]= n;            //T[i] の山を n 個にする
    InvalidateRect(hParent,NULL,TRUE);
    UpdateWindow(hParent);
    return;
}

// 山の石をカウント
int  Miyama::Stone()
{   int     n;
    n= T[0]+T[1]+T[2];
    return(n);
}

// コンピュータがプレイする
int   Miyama::Play()
{   int     n1;         //石が残っている山の数
    int     n2;         //石が一個だけの山の数
    int     w;          //石が一番多い山の番号
    int     i,n;

    for(i=n1=n2=0,w=0; i<3; i++)
    {   if (T[i])
        {   n1++;
            if (T[i]==1)    n2++;
            if (T[i]>T[w])  w= i;
        }
    }
    switch(n1)
    {   case 0: return(1);                  //三個の山が空(コンピュータの勝ち)
        case 1: //石が残っている山は一個
                if (n2==1)                  //最後の石(コンピュータの負け)
                {   yama= w;  down= 1;
                    return(2);
                }
                //一個を残して全て取り除く
                yama= w;  down= T[w]-1;
                return(0);
        case 2: //石が残っている山は二個
                if (n2==0)  break;
                yama= w;  down= T[w];
                return(0);
        case 3: //石が残っている山は三個 
                if (n2!=2)  break;
                yama= w;  down= T[w]-1;
                return(0);
    }
    w= T[0]^T[1]^T[2];
    for(i=0; i<3; i++)
    {   n=T[i]^w;
        if (n<T[i]) break;
    }
    if (i>2)
    {   while(T[i=rand()%3]==0);
        n=rand()%T[i];
    }
    else if (n1==3 && rand()%4==0 && T[0]+T[1]+T[2]>4)
    {   while(T[i=rand()%3]<=1);
        n=rand()%(T[i]-1)+1;
    }
//    sprintf(msg,"私は %d の山から %d 個取ります。",i+1,T[i]-n);
    yama= i;  down= T[i]-n;
    return(0);
}

// 石を一個ずつ取り除くアニメーションをする
int   Miyama::CntDown()
{   if (down<=0)     return(1);
    down--;
    T[yama]--;
    InvalidateRect(hParent,&INV_R[yama],TRUE);
    UpdateWindow(hParent);
    if (down<=0)     return(1);
    return(0);
}

// 棋譜を登録する
WORD  Miyama::Log(WORD c)
{   LG[c][0]= T[0];
    LG[c][1]= T[1];
    LG[c][2]= T[2];
    CNT= c;
    CNT++;
    return(CNT);
}

// 棋譜を再現する
void  Miyama::Saigen(short num)
{   T[0]= LG[num-1][0];
    T[1]= LG[num-1][1];
    T[2]= LG[num-1][2];
    InvalidateRect(hParent,NULL,TRUE);
}

// クリック位置から山,石を計算する
int   Miyama::ItemNo(POINT *pt)
{   int     i;
    for(i=0; i<3 && pt->x>Box[i].right; i++);
//Debug("i",i);  Debug("pt->x",pt->x);  Debug("pt->y",pt->y);
    if (i>=3 || pt->x<Box[i].left || pt->y<Box[i].top || pt->y>Box[i].bottom)
        return(0);
    pt->y= ((pt->x-Box[i].left)/Width)*5+(4-(pt->y-Box[i].top)/Height);
    pt->x= i;
    return(1);
}

// 三個の山に石を設定
void  Miyama::Settbl()
{   int     i;
    while(1)
    {   for(i=0; i<3; i++)  T[i]=rand()%15+1;
        if (T[0]!=T[1] && T[0]!=T[2] && T[1]!=T[2])  return;
    }
}

// BMP ファイルをロードする
void  Miyama::LoadBMP(HWND hWnd, LPCSTR szBitmap, long *Width, long *Height)
{   HDC         hdc;
    HBITMAP     hbm;
    BITMAP      bmp;
    char        emsg[80];

    // ビットマップリソースからの読み込み
    hbm= (HBITMAP)LoadImage(GetModuleHandle(NULL),szBitmap,IMAGE_BITMAP,
                            0,0,LR_CREATEDIBSECTION);
    // BMPファイルからの読み込み
    if (hbm==NULL)
        hbm= (HBITMAP)LoadImage(NULL,szBitmap,IMAGE_BITMAP,0,0,
                                LR_LOADFROMFILE|LR_CREATEDIBSECTION);
    if (hbm==NULL)
    {   wsprintf(emsg,"LoadBMP Error [%s]",szBitmap);
        MessageBox(NULL, emsg, "LoadBMP", MB_OK);
        return;
    }
    hdc = GetDC(hWnd);
    hMdc = CreateCompatibleDC(hdc);
    SelectObject(hMdc, hbm);
    GetObject(hbm, sizeof(BITMAP), &bmp);
    *Width = bmp.bmWidth;
    *Height = bmp.bmHeight;
    DeleteObject(hbm);
    DeleteDC(hdc);
}

HFONT Miyama::SetMyFont(HDC hdc,LPCTSTR face,int h,int angle)
{   HFONT   hFont;
    hFont=  CreateFont(h,       //フォント高さ
        0,                      //文字幅(高さと同じ)
        angle,                  //テキストの角度
        0,                      //ベースラインとx軸との角度
        FW_REGULAR,             //フォントの重さ(太さ)
        FALSE,                  //イタリック体
        FALSE,                  //アンダーライン
        FALSE,                  //打ち消し線
        SHIFTJIS_CHARSET,       //文字セット
        OUT_DEFAULT_PRECIS,     //出力精度
        CLIP_DEFAULT_PRECIS,    //クリッピング精度
        PROOF_QUALITY,          //出力品質
        FIXED_PITCH|FF_MODERN,  //ピッチとファミリー
        face);                  //書体名
    return hFont;
}

Miyama Game のサウンドプログラム Audio.cpp です。
//★ Midi, Wave 共通の Audio Object を定義する     2002-04-20
#include    "miyama.h"

//★★ Audio Object Class 関数
// Constructor(オブジェクトの初期化)
Audio::Audio()
{   g_pLoader= NULL;
    g_pPerformance= NULL;
    g_pPerformance2= NULL;
    g_pSegment= NULL;
    InitDirectAudio();
}
// Destructor(オブジェクトの終了)
Audio::~Audio()
{   FreeDirectAudio();
}

//★Load Midi Wave File
HRESULT  Audio::Load(LPSTR Audio, IDirectMusicSegment8** pSegment,
                     IDirectMusicPerformance8* pPerform)
{   HRESULT hr;
    WORD    i;

    if (NULL != pPerform)
    {   pPerform->Stop(NULL,NULL,0,0);
        if (NULL != *pSegment)
            (*pSegment)->Unload(pPerform);
    }
    for(i=0; i<strlen(Audio); i++)  AudioFile[i]= Audio[i];
    AudioFile[i]= 0;
    // Load the segment
    hr = LoadSegment(pSegment);
    if (FAILED(hr))
    {   ERRMSG("Could not load segment");  return hr;  }
    (*pSegment)->Download(pPerform);
    return S_OK;
}

//★Load Midi Wave Resource
HRESULT  Audio::Load(HMODULE hMod, WORD ResourceID,
                     IDirectMusicSegment8** pSegment,
                     IDirectMusicPerformance8* pPerform)
{   HRESULT hr;
    if (NULL != pPerform)
    {   pPerform->Stop(NULL,NULL,0,0);
        if (NULL != *pSegment)
            (*pSegment)->Unload(pPerform);
    }
    // Load the segment
    hr = LoadRes(hMod,ResourceID,pSegment);
    if (FAILED(hr))
    {   ERRMSG("Could not load segment");  return hr;  }
    (*pSegment)->Download(pPerform);
    return S_OK;
}

//★StopPlay
void  Audio::Play(int rep,IDirectMusicSegment8* pSegment,IDirectMusicPerformance8* pPerform)
{   // set the segment to repeat many times
    pSegment->SetRepeats(rep);
    pPerform->PlaySegmentEx(pSegment,NULL,NULL,0,0,NULL,NULL,NULL);
}

//★StopAudio
void  Audio::Stop(IDirectMusicPerformance8* pPerform)
{   if (NULL != pPerform)
        pPerform->Stop(NULL,NULL,0,0);
}

//★SetVol
void  Audio::SetVol(IDirectMusicPerformance8* pPerform, long *nVol)
{   if (NULL != pPerform)
    pPerform->SetGlobalParam(GUID_PerfMasterVolume,(void*)nVol,sizeof(long));
}

//★InitDirectAudio()
HRESULT Audio::InitDirectAudio()
{   HRESULT hr;

    hr= CoInitialize(NULL);
    if (FAILED(hr))
    {   ERRMSG("Could not initialize COM");  return hr;   }
    // Create loader object
    hr= CoCreateInstance(CLSID_DirectMusicLoader,NULL,CLSCTX_INPROC, 
                         IID_IDirectMusicLoader8,(void**)&g_pLoader);
    if (FAILED(hr))
    {   ERRMSG("Could not create DMusic Loader");  return hr;  }
    // Create performance object
    hr= CoCreateInstance(CLSID_DirectMusicPerformance,NULL,CLSCTX_INPROC,
                    IID_IDirectMusicPerformance8,(void**)&g_pPerformance);
    hr= CoCreateInstance(CLSID_DirectMusicPerformance,NULL,CLSCTX_INPROC,
                    IID_IDirectMusicPerformance8,(void**)&g_pPerformance2);
    if (FAILED(hr))
    {   ERRMSG("Could not create DMusic Performance");  return hr;  }
    // Initialize the software synthesizer
    hr= InitializeSynth();
    if (FAILED(hr))     return hr;
    return S_OK;
}

//★InitializeSynth()
HRESULT Audio::InitializeSynth()
{   HRESULT hr;

    hr= g_pPerformance->InitAudio(NULL,NULL,NULL,DMUS_APATH_SHARED_STEREOPLUSREVERB,
                                  64,DMUS_AUDIOF_ALL,NULL);
    hr= g_pPerformance2->InitAudio(NULL,NULL,NULL,DMUS_APATH_SHARED_STEREOPLUSREVERB,
                                  64,DMUS_AUDIOF_ALL,NULL);
    if (FAILED(hr))
    {   ERRMSG("Could not initialize performance");  return hr;  }
    return S_OK;
}

//★LoadSegment()
HRESULT Audio::LoadSegment(IDirectMusicSegment8** pSegment)
{   HRESULT     hr;
    DMUS_OBJECTDESC ObjDesc; // Object descriptor for pLoader->GetObject()

    // sections load as type Segment, as do MIDI files, for example.
    ObjDesc.guidClass = CLSID_DirectMusicSegment;
    ObjDesc.dwSize = sizeof(DMUS_OBJECTDESC);
    wcscpy(ObjDesc.wszFileName,AudioFile);
    ObjDesc.dwValidData= DMUS_OBJ_CLASS | DMUS_OBJ_FILENAME;
    hr= g_pLoader->GetObject(&ObjDesc,IID_IDirectMusicSegment8,(void**)pSegment);
    return hr;
}

//★Resource から Midi,Wave を取得
HRESULT  Audio::LoadRes(HMODULE hMod, WORD ResourceID,IDirectMusicSegment8** pSegment)
{   HRESULT         hr;
    DMUS_OBJECTDESC ObjDesc;

    HRSRC hFound= FindResource(hMod,MAKEINTRESOURCE(ResourceID),"WAVE");
    if (hFound==NULL)
    {   ERRMSG("Not Found Wave(Midi) Resource");
        return  S_FALSE;
    }
    HGLOBAL hRes= LoadResource(hMod,hFound);
    ObjDesc.dwSize = sizeof(DMUS_OBJECTDESC);
    ObjDesc.guidClass = CLSID_DirectMusicSegment;
    ObjDesc.dwValidData = DMUS_OBJ_CLASS | DMUS_OBJ_MEMORY;
    ObjDesc.pbMemData = (BYTE *) LockResource(hRes);
    ObjDesc.llMemLength = SizeofResource(hMod, hFound);
    hr= g_pLoader->GetObject(&ObjDesc,IID_IDirectMusicSegment8, 
                   (void**)pSegment);
    return hr;
}

//★FreeDirectAudio()
HRESULT Audio::FreeDirectAudio()
{   // Release the Segment
    if (NULL != g_pSegment)
    {   g_pSegment->Release();  g_pSegment= NULL;  }
    // If there is any music playing, stop it.
    if (NULL != g_pPerformance)
    {   g_pPerformance->Stop(NULL,NULL,0,0);
        // CloseDown and Release the performance object
        g_pPerformance->CloseDown();
        g_pPerformance->Release();
        g_pPerformance = NULL;
    }
    if (NULL != g_pPerformance2)
    {   g_pPerformance2->Stop(NULL,NULL,0,0);
        g_pPerformance2->CloseDown();
        g_pPerformance2->Release();
        g_pPerformance2 = NULL;
    }
    // Release the loader object
    if (NULL != g_pLoader)
    {   g_pLoader->Release();   g_pLoader= NULL;  }
    // Release COM
    CoUninitialize();
    return S_OK;
}

[Next Chapter ↓] 三つの山に石を表示

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