サーバーを介してオセロで対戦

最大10組(20名)の Client が Server を介してオセロゲームで対戦します。

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

プロジェクトの概要

  1. 最大10組(20名)の Client が Server を介して「オセロゲーム」で対戦します。
    「オセロゲーム」のプログラムは オセロゲームの盤と駒を表示 クリックされた場所に駒を置く などを参考にして下さい。
  2. Client 同士の接続を管理して「オセロゲーム」で対戦する Server Program を DOS モードで作成します。
    1. Server を立ち上げて Client からの接続待ちで待機します。
    2. 接続してきた Client を先着順に「1番の親」「1番の子」「2番の親」「2番の子」・・・に Login します。
      親/子は便宜上の呼称で、先手/後手とは関係ありません。
    3. Client はIDで管理し、親と子が揃うと先手/後手を決めてゲームを開始します。
    4. Server は親/子に対して交互にプレイを促し、現在の盤の状態と進行メッセージを送信します。
      プレイやパスや終局は Server が管理し Client に通知します。
    5. 双方の打てる箇所が無くなったら終局です。
      黒と白の駒をカウントして Client に知らせます。
    6. Client 側の YES ボタンをクリックすると再挑戦できます。
  3. Client のプログラムは Windows モードで作成します。
    1. Server が立ち上がっていることを確認して Login します。
      Server の IP Address は FTP サーバーを構築する を参照して下さい。
    2. 「親/子」が揃うと先手/後手の確認メッセージが送られてきます。
      先手/後手を交代するときは NO ボタンをクリックして下さい。
      YES ボタンでゲームを開始します。
    3. ゲームの進行や盤の管理は全て Server 側で行うので、Client は Server の指示に従ってプレイします。
    4. 手番になると盤上の駒を置く座標をクリックします。
      Play COMMAND と座標がサーバーに送信されて、挟んだ駒が裏返されます。
    5. プレイやパスや終局は Server が管理し Client に通知します。
      終局になれば黒と白の駒をカウントして Client に知らせます。
    6. 再挑戦するときは Client 側の YES ボタンをクリックして下さい。

Client 情報と COMMAND

  1. Server が管理する各 Client の構造体です。
        //☆Client 構造体
        typedef struct _TABLE
        {   char    ID[2][24];      //先手 ID, 後手 ID
            char    T[8][8];        //OSEROの盤を定義
            short   TEBAN;          //手番(0:待ち 1:黒番 2:白番)
            char    MSG[2][80];     //送信メッセージ
            short   FLAG[2];        //盤面更新フラグ
        }   TABLE;
        
  2. char ID[2][24];
    Client はIDで管理されます。
    対戦する「親と子」のIDを格納します。
    ID の先頭が '0' のときは Login されていません。
  3. char T[8][8];
    対戦中の盤を格納します。
    説明
    0 駒が置かれていない状態です。
    1 黒の駒が置かれた状態です。
    -1 白の駒が置かれた状態です。
  4. short TEBAN;
    対戦の進行状況と手番を管理します。
    1. 0:
      ゲーム対戦の準備中です。
      親だけが Login した状態や、先手/後手が決まっていない状態です。
    2. 1:
      黒(親)の手番です。
      手番を変更したときは ID[2][24] も入れ替えて、必ず親が先手(黒)になるように設定します。
    3. 2:
      白(子)の手番です。
      黒の手番(1)と白の手番(2)を交互に切り替えます。
    4. 9:
      ゲームが終了した状態です。
  5. char MSG[2][80];
    Client からはタイマ割り込みで Server に対して常に現在の状態を問い合わせてきます。
    このとき Client に送信するメッセージを格納します。
  6. short FLAG[2];
    Client からの問い合わせに対して、盤の状態が変化していれば T[8][8] を Encode して送信します。
    FLAG は盤の状態を更新したとき ON に設定し、Client に送信すれば OFF にします。
  7. Client から送られてくるメッセージ(COMMAND)は、先頭の一文字で識別しています。
    Client から送られてくる COMMAND の仕様です。
    詳しい説明は SnedMsg() 関数を参照して下さい。
    COMMAND 引数-1 引数-2 引数-3 説明
    LOGIN ID ゲームに参加する
    END ID ゲームを終了する
    PLAY ID Y座標 X座標 Y,X座標に駒を置く
    GET ID 現在の状態を問い合わせる
    YES ID メッセージの確認
    NO ID 手番の変更
  8. Server から送られてくる COMMAND に対する応答メッセージの形式です。
    Client はタイマ割り込みで Server に対して常に現在の状態を問い合わせます。
    先頭が「=」で始まるメッセージは、更新されたオセロ盤の状態です。
    盤の状態が変化していれば T[8][8] の盤を16進数に変換して送ってきます。
    Client は Encode で変換された16進数を元に戻して盤を表示します。
    それ以外のメッセージは「手番やパスや終局」などのプレイヤに対する指示で、ページ先頭の画像のように StatusBar に表示します。

Server Program の説明

  1. Server のプログラムは、画像の表示にこだわらないので DOS モードで作成しました。
    Osero Net Server Object Class の Header です。
    Wsock Object Class を継承します。
        /***********************************************************/
        /*★ Osero Net Game Saver Object Class Header   前田 稔 ★*/
        /***********************************************************/
        #ifndef     _OseroNetS
        #define     _OseroNetS
        #include    <stdio.h>
        #include    <winsock2.h>
        #include    "Wsock.h"
    
        //☆Client 構造体の定義
        typedef struct _TABLE
    
        class OseroNetS : public Wsock
        { //★非公開部分
          protected:
            TABLE   TBL[10];                //Client の対戦情報
            char    msg[1024];              //送信メッセージ
            short   POS;                    //TBL の番号
            short   NUM;                    //先手, 後手
            char    WID1[24],WID2[24];      //Work ID
    
          //★公開部分
          public:
            char    WT[6][24];              //Work Table
            BYTE    SRHT[32];               //Osero Search
            OseroNetS();                    //Constructor
            ~OseroNetS();                   //Destructor
            int     SendMsg(char *com);
            short   SetStr(char buf[], char t[][24]);
            void    Scan(char id[]);
            void    Play(char yp[], char xp[]);
            short   Check(WORD x, WORD y, short c, char t[8][8]);
            short   Reverse(WORD x, WORD y, short c, char t[8][8]);
            short   Search(short c, BYTE str[], char t[8][8]);
            void    Encode(char t[8][8], char str[]);
            void    Decode(char t[8][8], char str[]);
            short   ChkTeban();
            void    InitTBL(short pos, char id[24]);
        };
    
        #endif
        
  2. TABLE TBL[10];
    各 Client の情報で、10組(20人)分の領域を定義します。
  3. char msg[1024];
    送信メッセージの編集領域です。
  4. short POS;
    Client から送られてきた COMMAND の TBL 番号を格納します。
  5. short NUM;
    Client から送られてきた COMMAND の「親/子」を格納します。
  6. char WT[6][24];
    COMMAND を解析するための Work Table です。
  7. BYTE SRHT[32];
    打てる座標を検索して格納する作業領域です。
  8. int SendMsg(char *com);
    Client からのメッセージを解析する主となる関数です。
  9. short SetStr(char buf[], char t[][24]);
    Client から送られてきた COMMAND の文字列を t[][] に切り出します。
  10. void Scan(char id[]);
    Client 情報のテーブルを検索して、POS, NUM を設定します。
    POS>=10 のときは ID が見つかりません。
  11. void Play(char yp[], char xp[]);
    yp, xp の座標に駒を置いて、挟んだ駒を裏返します。
  12. short Check(WORD x, WORD y, short c, char t[8][8]);
    yp, xp に駒を置くことができるか調べます。
  13. short Reverse(WORD x, WORD y, short c, char t[8][8]);
    挟んだ駒を裏返します。
  14. short Search(short c, BYTE str[], char t[8][8]);
    手番(c) で全ての打てる箇所を検索します。
  15. void Encode(char t[8][8], char str[]);
    オセロ盤の情報を転送するために16進数に変換します。
  16. void Decode(char t[8][8], char str[]);
    Encode で変換された16進数を元に戻して盤に格納します。
  17. short ChkTeban();
    現在の手番とパスと終局を調べます。
    パスのときは手番を変更します。
  18. void InitTBL(short pos, char id[24]);
    Client の情報を初期化してオセロ盤を設定します。


Server Program の関数

  1. SendMsg() メンバー関数は Client からの COMMAND を解析してメッセージを送り返す関数です。
    この関数をオーバーライドして Client からの COMMAND を解析して下さい。
    オーバーライドの説明は Server Program を Windows で作成する を参照して下さい。
    com[] は Client から送られてきた COMMAND が格納されています。
    先頭が 'G' の COMMAND は、タイマ割り込みで頻繁に送信されてくる GET COMMAND です。
    GET 以外のコマンドを printf() でウインドウに表示しています。
    LOGIN 以外の COMMAND では ID が登録されていなければエラーです。
    LOGIN では TBL[] の空いている箇所を検索してIDを登録します。
    NUM==0 のときは親で、NUM==1 のときは子で登録します。
    親と子が揃えばゲームの開始です。
        //★ COMMAND を解析して Send Message を作成
        int  OseroNetS::SendMsg(char com[])
        {   int     n;
            short   rc;
    
            if (com[0]!='G')    printf("[%s]\n",com);
            SetStr(com,WT);
            Scan(WT[1]);
            if (POS>=10 && com[0]!='L' && com[0]!='l')
                sprintf(msg,"%s さんは Login されていません",WT[1]);
            else  switch(com[0])
            {   case 'L': case 'l':         //LOGIN ID
                    if (POS<10)
                    {   sprintf(msg,"%s さんは既に Login されています",WT[1]);  break;  }
                    Scan("\0");
                    if (POS>=10)
                    {   strcpy(msg,"現在ゲーム中で Login できません");  break;  }
                    if (NUM==0)
                    {   InitTBL(POS,WT[1]);
                        sprintf(msg,"%s さんは親で Login しました",WT[1]);
                        strcpy(TBL[POS].MSG[0],msg);
                    }
                    else
                    {   strcpy(TBL[POS].ID[1],WT[1]);
                        sprintf(msg,"%s さんの先手、%s さんの後手で良いですか",TBL[POS].ID[0],TBL[POS].ID[1]);
                        strcpy(TBL[POS].MSG[0],msg);
                        strcpy(TBL[POS].MSG[1],msg);
                    }
                    break;
        
  2. PLAY COMMAND が送られてくると Play() 関数を呼び出します。
    YES COMMAND は手番の確定と再挑戦のときに送られてきます。
    NO COMMAND は手番を変更するときに送られてきます。
    それ以外の YES, NO COMMAND は手番の確認です。
    END COMMAND で Logout します。
    親が Logout するときは、子を親にシフトして子を空にしています。
                case 'P': case 'p':         //PLAY ID YP XP
                    Play(WT[2],WT[3]);      //盤に駒を置く
                    break;
                case 'Y': case 'y':         //YES ID
                    if (TBL[POS].TEBAN>2)   //次のゲーム
                    {   strcpy(WID1,TBL[POS].ID[0]);
                        strcpy(WID2,TBL[POS].ID[1]);
                        InitTBL(POS,WID1);
                        strcpy(TBL[POS].ID[1],WID2);
                        sprintf(msg,"%s さんの先手、%s さんの後手で良いですか",TBL[POS].ID[0],TBL[POS].ID[1]);
                        strcpy(TBL[POS].MSG[0],msg);
                        strcpy(TBL[POS].MSG[1],msg);
                        break;
                    }
                    if (TBL[POS].TEBAN==0)  //手番の確認
                    {   sprintf(msg,"%s さんの先手でお願いします",TBL[POS].ID[0]);
                        strcpy(TBL[POS].MSG[0],msg);
                        strcpy(TBL[POS].MSG[1],msg);
                        TBL[POS].TEBAN= 1;
                        break;
                    }
                    rc= ChkTeban();         //手番 & Path & 終了をチェックする
                    strcpy(TBL[POS].MSG[NUM],msg);
                    if (rc>1)   strcpy(TBL[POS].MSG[1-NUM],TBL[POS].MSG[NUM]);
                    break;
                case 'N': case 'n':         //NO ID
                    if (TBL[POS].TEBAN>2)   break;
                    if (TBL[POS].TEBAN==0)  //先手後手の交代
                    {   strcpy(WID1,TBL[POS].ID[0]);
                        strcpy(TBL[POS].ID[0],TBL[POS].ID[1]);
                        strcpy(TBL[POS].ID[1],WID1);
                        TBL[POS].FLAG[0]=TBL[POS].FLAG[1]= 1;
                        sprintf(msg,"%s さんの先手、%s さんの後手で良いですか",TBL[POS].ID[0],TBL[POS].ID[1]);
                        strcpy(TBL[POS].MSG[0],msg);
                        strcpy(TBL[POS].MSG[1],msg);
                        break;
                    }
                    rc= ChkTeban();         //手番 & Path & 終了をチェックする
                    strcpy(TBL[POS].MSG[NUM],msg);
                    if (rc>1)   strcpy(TBL[POS].MSG[1-NUM],TBL[POS].MSG[NUM]);
                    break;
                case 'E': case 'e':         //END ID
                    if (POS<10)
                    {   sprintf(msg,"%s さんが Logout されました",WT[1]);
                        strcpy(WID1,TBL[POS].ID[1-NUM]);
                        InitTBL(POS,WID1);
                        strcpy(TBL[POS].MSG[0],msg);
                        break;
                    }
                    break;
        
  3. default: は GET COMMAND に対するメッセージの送信です。
    OSERO 盤が更新されたときは Encode して盤の情報を送信します。
    それ以外のときは TBL[] に保存されている Client ごとの MSG を送信します。
                default:                    //GET ID
                    if (TBL[POS].FLAG[NUM]) //OSERO 盤を送信
                    {   Encode(TBL[POS].T,msg);
                        TBL[POS].FLAG[NUM]= 0;
                        break;
                    }
                    strcpy(msg,TBL[POS].MSG[NUM]);
                    break;
            }
            // メッセージを送信します
            n = send(sockw,msg,strlen(msg),0);
            if (n < 1)
            {   strcpy(szMSG,"send に失敗しました");    return -1;  }
            strcpy(szMSG,msg);
            return 0;
        }
        
  4. Play() 関数では手番とエラーをチェックして盤に石を置いて挟んだ駒を裏返します。
    ゲームの操作性を考慮して、プレイの前後で「手番・パス・終局」を調べる ChkTeban() 関数を呼び出します。
        void  OseroNetS::Play(char yp[], char xp[])
        {   WORD    y,x;
            short   rc,c;
    
            if (TBL[POS].TEBAN!=NUM+1)
            {   strcpy(msg,"手番が来るまでもう少しお待ち下さい");
                strcpy(TBL[POS].MSG[NUM],msg);
                return;
            }
            rc= ChkTeban();
            if (rc!=1)  {  strcpy(TBL[POS].MSG[NUM],msg);   return;  }
            //rc==1 プレイ出来るとき
            y= atoi(yp);
            x= atoi(xp);
            c= 1;
            if (TBL[POS].TEBAN==2)  c= -1;
            if (Check(x,y,c,TBL[POS].T)==0)
            {   sprintf(msg,"Play Error (Y=%d  X=%d)",y,x);
                strcpy(TBL[POS].MSG[NUM],msg);
                return;
            }
            //盤に駒を置く
            Reverse(x,y,c,TBL[POS].T);
            sprintf(TBL[POS].MSG[NUM],"%s Play (Y=%d  X=%d)   ",TBL[POS].ID[NUM],y,x);
            TBL[POS].TEBAN^= 3;
            TBL[POS].FLAG[0]=TBL[POS].FLAG[1]= 1;
            rc= ChkTeban();                 //Path & 終了 Check
            strcat(TBL[POS].MSG[NUM],msg);
            strcpy(TBL[POS].MSG[1-NUM],TBL[POS].MSG[NUM]);
            if (rc==2)  TBL[POS].TEBAN^= 3; //パスのとき、元に戻す
        }
        
  5. 「手番・パス・終局」を調べる ChkTeban() 関数です。
    手番がパスのときは手番を変更しています。
        // 手番 & Path & 終了をチェックする
        short  OseroNetS::ChkTeban()
        {   short   ban,c,bc,wc,x,y;
    
            ban= TBL[POS].TEBAN;
            if (ban==0)
            {   strcpy(msg,"GAME 対戦の設定中です");    return 0;  }
            if (ban<3)
            {   c= 1;
                if (ban==2)     c= -1;
                if (Search(c,SRHT,TBL[POS].T))
                {   sprintf(msg,"次は %s さんの手番です",TBL[POS].ID[ban-1]);
                    return 1;
                }
                if (Search(-c,SRHT,TBL[POS].T))
                {   sprintf(msg,"※%s さんはパスです",TBL[POS].ID[ban-1]);
                    TBL[POS].TEBAN^= 3;
                    return 2;
                }
            }
            //Game Over
            bc=wc= 0;
            for(y=0; y<8; y++)
            {   for(x=0; x<8; x++)
                {   if (TBL[POS].T[y][x]==1)    bc++;
                    if (TBL[POS].T[y][x]==-1)   wc++;
                }
            }
            sprintf(msg,"★Game Over (黒=%d  白=%d)",bc,wc);
            TBL[POS].TEBAN= 9;
            return 3;
        }
        
  6. これ以外の関数は、他のページを参考にして各自で作成して下さい。

Client Program の説明

  1. Client のプログラムは図形を使うので Windows モードで作成します。
    Osero Net Game Object Class の Header です。
    Wsock Object Class を継承します。
    Client のプログラムは、基本的に Server とのメッセージの送受信とウインドウの描画だけです。
        /******************************************************/
        /*★ Osero Net Game  Object Class Header   前田 稔 ★*/
        /******************************************************/
        #ifndef     _OseroNet
        #define     _OseroNet
        #include    <commdlg.h>
        #include    "Wsock.h"
    
        class OseroNet : public Wsock
        { //★非公開部分
          protected:
            char    T[8][8];                //OSEROの盤を定義
            HPEN    bPen,wPen,grPen;        //黒、白、灰色のペン
            HBRUSH  bBrush,wBrush,gBrush;   //黒、白、緑のブラシ
            RECT    rcBox;                  //盤の罫線部分
            short   KSIZE,WSIZE;            //駒の直径,枠のサイズ
            //非公開関数定義
            void    Koma(HDC, short, short, HBRUSH, HBRUSH);
    
          //★公開部分
          public:
            HWND    hWnd;
            short   TEBAN;                  //手番(1:黒  -1:白)
            WORD    Ypos,Xpos;              //OSERO盤の座標
            //公開関数定義
            OseroNet(HWND hwnd);            //Constructor
            ~OseroNet();                    //Destructor
            void    WinInit();
            void    Disp(HDC);
            BYTE    SetPos(POINT pt);
            short   Check(WORD x, WORD y, short c, char t[8][8]);
            short   Check(short c) { return(Check(Xpos, Ypos, c, T)); }
            int     Reverse(UINT x, UINT y, int c, char t[8][8]);
            int     Reverse(int c) { return(Reverse(Xpos, Ypos, c, T)); }
            void    Encode(char str[]);
            void    Decode(char str[]);
        };
    
        #endif
        
  2. WinMain Program の最初の部分です。
    OseroNet *App= NULL; が Osero Net Object Class の宣言です。
    char smsg[80]; には Status Bar に表示する Message を格納します。
    Status Bar に表示する TEXT は描画するときに参照されるようです。
        #include    <windows.h>
        #include    <commctrl.h>
        #include    <shlobj.h>
        #include    "OseroNet.h"
        #include    "resource.h"
        #pragma     once
        #pragma     comment(lib,"ws2_32.lib")
    
        #define     ID_TIMER    32767
        #define     ID_STATUS   1001
    
        OseroNet    *App= NULL;                     //Osero Net Object Class
        HWND        g_hWnd;                         //Windows Handle
        HINSTANCE   g_hInst;                        //Instance Hnadle
        HWND        g_hSTS;                         //Status Bar Handle
        HWND        g_hYes,g_hNo,g_hEnd;            //Button Handle
        char        URL[MAX_PATH]= "127.0.0.1";     //送信先 URL
        int         PORT= 12345;                    //送信先 PORT
        char        ID[24]= "PlayerID";             //Player ID
        char        buf[1024];                      //送受信 Message
        char        old[80];                        //同一メッセージの判定用
        char        smsg[80];                       //Status Bar Message
    
        //プロトタイプ宣言
        LRESULT  CALLBACK   WndProc(HWND, UINT, WPARAM, LPARAM);
        int      PASCAL     WinMain(HINSTANCE, HINSTANCE, LPSTR, int);
        LRESULT  CALLBACK   DlgProc(HWND hDlg,UINT msg,WPARAM wParam,LPARAM lParam);
        
  3. マウスのクリックで PLAY COMMAND をサーバーに送信します。
            case WM_LBUTTONDOWN:
                pt.x= LOWORD(lParam);
                pt.y= HIWORD(lParam);
                App->SetPos(pt);                //クリック位置から盤の座標を計算
                wsprintf(buf,"PLAY %s %d %d",ID,App->Ypos,App->Xpos);
                App->Connect(URL,PORT,buf,buf,1024);
                strcpy(smsg,buf);
                InvalidateRect(hWnd,NULL,TRUE);
                break;
        
  4. メニュー操作(ボタン操作)で送信される COMMAND です。
    LOGIN では DialogBox から ID/URL/PORT を設定して COMMAND を送ります。
            case WM_COMMAND:
                KillTimer(hWnd, ID_TIMER);
                switch(LOWORD(wParam))
                {   case IDM_LOGIN:
                        if (App->Startup()==-1)
                        {   strcpy(smsg,App->szMSG);
                            return(FALSE);
                        }
                        rc= DialogBox(g_hInst,MAKEINTRESOURCE(IDD_DIALOG1),NULL,(DLGPROC)DlgProc);
                        if (rc==IDC_LOGIN)  SetTimer(hWnd,ID_TIMER,2000,NULL);
                        InvalidateRect(hWnd,NULL,TRUE);
                        return(FALSE);
                    case IDM_YES:
                        wsprintf(buf,"YES %s",ID);
                        App->Connect(URL,PORT,buf,buf,1024);
                        strcpy(smsg,buf);
                        InvalidateRect(hWnd,NULL,TRUE);
                        SetTimer(hWnd,ID_TIMER,2000,NULL);
                        return(FALSE);
                    case IDM_NO:
                        wsprintf(buf,"NO %s",ID);
                        App->Connect(URL,PORT,buf,buf,1024);
                        strcpy(smsg,buf);
                        InvalidateRect(hWnd,NULL,TRUE);
                        SetTimer(hWnd,ID_TIMER,2000,NULL);
                        return(FALSE);
                    case IDM_HELP:
                        ShellExecute(NULL,"open","oseronet.hlp","","",SW_SHOW);
                        break;
                    case IDM_VERSION:
                        MessageBox(NULL,"Osero Net Game Ver 1.0  Maeda Minoru",NAME,MB_OK);
                        break;
                    case IDM_END:
                        SendMessage(hWnd,WM_CLOSE,0,0L);
                        break;
                }
                break;
        
  5. WM_TIMER: で定期的に GET COMMAND をサーバーに送信して最新情報を受け取ります。
    先頭が '=' のときは Decode してオセロ盤を描画します。
    これ以外のメッセージは StatusBar に表示します。
            case WM_TIMER:
                KillTimer(hWnd, ID_TIMER);
                strcpy(buf,"GET ");
                strcat(buf,ID);
                rc= App->Connect(URL,PORT,buf,buf,1024);
                if (rc || strcmp(buf,old)==0)
                {   SetTimer(hWnd,ID_TIMER,300,NULL);
                    break;
                }
                strcpy(old,buf);
                if (rc==0 && buf[0]=='=')   App->Decode(buf);
                strcpy(smsg,buf);
                InvalidateRect(hWnd,NULL,TRUE);
                SetTimer(hWnd,ID_TIMER,300,NULL);
                break;
        
  6. これ以外の関数は、他のページを参考にして各自で作成して下さい。

【課題】

  1. このプログラムは一応完成していますが、幾つかの問題点や改善したい項目があります。
  2. 既に Login されているIDを使うとエラーは表示されますが、その人と同じ立場で Login できます。
    これを防ぐには幾つかの方法が考えられます。
    1. Login 時にIDに加えてパスワードを使う。
      パスワードの代わりに接続時刻や乱数を使う方法もある。
    2. Login 時にサーバー側から認識番号を送る。
    3. accept した Client を識別する。
      SOCKET  sock0;
      SOCKET  sock;
      struct  sockaddr_in client;
      int     len;
      
          len = sizeof(client);
          sock = accept(sock0, (struct sockaddr *)&client, &len);
      
          printf("accepted connection from %s, port=%d\n",
                  inet_ntoa(client.sin_addr), ntohs(client.sin_port));
      
  3. このままでは対戦相手がこなければゲームができません。
    そこでサーバーを相手にゲームする機能を追加して下さい。
    サーバー側の強さレベルを選択できるともっと良いのですが (^_^;

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