スピードはトランプのゲームです。ここではスピードのルールを説明しますが、そのルールは地方によって様々であったり、オプションのように付け加える事ができるルールがあったりするようです。その点に関しては、今回は特にこだわる必要がないので、ジョーカー2枚を含めるルールの中で最もシンプルなルールを適用しました。
場に置かれた2枚のカードを見て、両方のカードの数字を+1か−1する。ただし、Kを+1すると1になり、1を−1するとKになる。この際にカードの種類は問わない。図では場にダイヤの1が置いてあるのでこれに対して、K
と2
の数字が書いてあるカードが相当し、スペードの5
が場においてあるのでこれに対して、4
と6
の数字が書いてあるカードが相当する。
もし1の条件に相当するカードが手札にあった場合、それを場に重ねて置いてよい。
2枚のジョーカーは例外として、全ての種類のカードに対しておく事ができる。また、場にジョーカーが置いてあった場合は、その上に全ての種類のカードを重ねる事ができる。
ゲームの進行中、手札のカードが3枚以下になったとき、いつでも自分の山のカードから補充してよい。ただし手札のカードは4枚が最大枚数であるので、これを超えて補充できない。
山・手札が残っている状態で、両者とも場にカードが置けなくなった場合、ゲーム開始のときと同様に、お互いの山カードから一枚づつ手に持ち、このカードを同タイミングで場に置いた時点でゲームが再開される。このとき、山カードがない場合は手札から出してよい。
1〜4を山と手札のカードの両方が無くなるまで続け、先に無くなった方が勝ちとなる。
はじめに本プログラムを「Speed」と呼び、開発をはじめました。トランプゲームのスピードはその名の通り、スピードを競うものです。プログラムでは54種類のカードを分かりやすく表示し、かつそのカードが操作しやすくなくてはなりません。よって、カードを画像として表示し、それをマウスで操作できるようにしたいと考えました。この事が容易に実現できるものがWindowsのGUIというものです。Windows環境のプログラミング言語には、Microsoft社の「Visual C++」「Visual Basic」、Borland社の「C++ Builder」等が挙げられますが、最も詳細にプログラムが作成でき、作者も使い慣れている「Visual C++ 6.0 」を選択しました。
また2台のコンピュータがネットワーク通信をして、スピードを対戦できるよう、通信プロトコルを使用します。プロトコルには主にTCPとUDPがあり、正確性には劣るが速度が早いUDPを使用して作成しました。
プログラムとプロトコルについては、順に説明していきます。それでは作成したプログラムの使い方を、使用例をもとに説明します。
Speedを実行すると次のような画面が現れます。
主な機能の紹介
メニューの種類は大まかに分けて、「ゲーム」、「設定」、「ヘルプ」に分類しました。ゲームのメニューにはアプリケーションの動作に関わるものを入れ、設定のメニューにはiniファイルの設定を変更するものを入れ、ヘルプのポップアップメニューには使い方やバージョン情報などを入れました。
![]() |
iniファイルでIPアドレスとポート番号が設定されている場合、相手との接続を始めます。接続手順は、まずはクライアントとして接続できるかを試みます。接続できればそのまま通信開始となりますが、接続できない場合、サーバとなりクライアント接続を待ちます。このとき、時間を計測し、設定している待機時間を超えてしまった場合は接続失敗として終了します。時間内にクライアントから接続があれば、通信開始となります。メッセージの受送信の関数はサーバかクライアントかによって異なるので、どちらで開始したのかを覚えておかなければなりません。Speedでは変数で記憶されています。使用される関数 クライアント:send() recv() サーバ :sendto() recvfrom() |
ネットワークの実験を行うために作成したもので、通信が開始された後に使用することができます。指定したバイトのパケットを指定した回数送信して、その往復にかかった時間を算出するものです。パケットの送り方は複数あり、 バイトを1セットとし繰り返し送信するものと、バイト数は固定として繰り返し送信する2つの方法です。どちらかのエディットボックスに任意の値を入力して送信すると結果のダイアログボックスが出てきます。
2のx乗バイトを1セット送信した場合 | 1500バイトを13回送信した場合 |
setup.iniの設定を行うメニューです。ゲーム設定ではゲームの地方ルールなどの設定をしたかったのですが、今回は実装には至りませんでしたので省略します。
対戦者IPアドレス | IPアドレスと、ホスト名の両方に対応 |
対戦者を待つ時間 | 接続を試みてから、相手からの応答がなかった場合に、接続をやめるまでのタイムアウト時間を設定します。 |
使用ポート | プライベートポート(ユーザが好きなように使えるポート)を使用します。通常であれば50001番が元からデフォルト値で入っています。別のポート番号を使用したければ、変更しても構いませんが、対戦相手も同じ番号を使用する必要があります。ウイルス駆除ソフト等をインストールしている場合は、ファイアーウォールの設定で、50001番ポートを通るようにする必要があります。 |
ゲームが開始されてからのお互いの通信内容が記述されていく部分です。メッセージのバイト数が大きい場合は画面には表示しきれないので、スクロールバーで移動してメッセージを確認します。これにより、いつ、どんなメッセージが受信されたか、送信されたかが分かるようになります。
詳しくは3−1の2で説明がありますのでそちらをご覧下さい。
下の図を説明すると、左下に裏側になっているカードが山、下の段に表側になっているカードが手札、上の段に2枚あるカードが場です。カードの操作は全てマウスで行います。ゲーム中の操作は、
1.手札のカードを場に置く
2.場のカードを手札に置く
この2種類のみです。両方とも操作方法は同じで、基本的に「つまむ」「移動する」「離す」の3つの動作をマウスで行います。まず「つまむ」には、マウスポインタをカードの画像上に持っていき、左ボタンを押します。このとき左ボタンは押したままにしてください。「移動する」にはつかんだカードを置きたい場所までドラッグします。最後に「離す」とき、押しっぱなしになっているマウスの左ボタンを離して完了です。
もしマウスの左ボタンを離すときに、カードの画像上ではないところで離してしまった場合は、動作が無効になります。なお移動に成功するとカードは消えますが、下の図のように点線の囲いができますので、山から手札にカードを持ってくるときは、点線を目安にしてください。
ゲーム開始からの経過時間を随時表示されます。タイマーの詳詳細は3−1の1で説明がありますのでそちらをご覧下さい。
今回のプログラムで最も欠かせない部分が、ネットワーク通信です。コンピュータをはじめとするや情報機器が通信を行うには、多くの手順を必要とします。その手順の役割を7つの層に分類したものが、「OSI参照モデル」といいます。私達が電話をかける際に、どのような手順で通信しているのかを知らなくても、電話の使い方さえ知っていれば通信ができるのと同じで、手順を分類する事によって、各層は全ての動作を知らなくても、すぐ一つ下の層の使い方さえ知っていれば、通信ができるという事になります。その役割は次の通りです。
物理層 | 通信回路に0か1かの電気的な信号を送信する |
データリンク層 | 通信相手との物理的な通信路を確保し、通信路に流れるデータのエラー検出などを行う |
ネットワーク層 | 相手にデータを届けるための通信経路の選択や、通信経路内のアドレスを管理する |
トランスポート層 | 相手まで確実に、または効率よくデータを届けるためのデータ圧縮や、誤り訂正や、再送信要求などを行う |
セッション層 | プログラム同士がデータの送受信を行うための仮想的な経路の確立や開放を行う |
プレゼンテーション層 | セッション層から受け取ったデータを、ユーザに分かりやすい形に変換したり、アプリケーション層から送られてくるデータを通信に適した形式に変換する |
アプリケーション層 | ユーザの操作により、プログラムが接続処理を行う、いわばプログラム間の通信 |
ネットワークを利用するコンピュータはWindowsに限ったことではありません。MacやLinuxやUNIXなど、異なる種類のOSが接続する可能性があります。またPCのみならず、携帯電話や家電製品からの接続があるかもしれません。様々な種類の機器を通信させる場合には、どのような手順で通信をするのかという約束事を定める必要があります。その約束事の集まりのことをプロトコルと呼び、物理層以外の全てに存在します。
トランスポート層のプロトコルで代表的なものにTCP(Transmission Control Protocol)と、UDP(User Datagram Protocol)の二つが挙げられます。現在、ほとんどの通信に使われているプロトコルがTCPであり、私達がホームページの閲覧する際に使用するHTTP、サーバの特定の場所ににファイルをアップロード・ダウンロードするFTP、メールの送受信を行うSMTP、POPなどは、全てTCPで行われています。一方UDPの方は、LAN内で利用されるアプリケーションや、共有ファイルの送受信や、ネットワークでコンピュータの時計を合わせるソフトウェアなどに使用されているようです。TCPとUDPの特性は異なるので、その使用用途によってプロトコルを選択する必要があります。
TCPの特徴 | UDPの特徴 |
エラー訂正・再送信処理がある | 送ったデータが相手に届いたか保障されない |
一度に大量のデータを送信できる | 一度に大量のデータを送信できない |
通信速度が遅い | 通信速度が速い |
TCPは時間がかかるが、相手に確実にデータを送信する事ができ、UDPは送信時間が速いが、相手に確実にデータが送られているか分からない、ということが言えます。今回作成するSpeedは一回のメッセージが最大でも256[Byte]を越える事はない小さいサイズであるということと、速度を重視するという点で、UDPを選択しています。その代わりに、パケットの通信中に解読不明なメッセージが来たり、一部欠けてしまっているメッセージが来た場合には、切り捨ててしまうか、再送信を行わなければなりません。この点に関しては、ゲーム中カードは常に1枚しか移動する事ができないという事を利用して、複数のカードが更新されたり、解読不能な命令文のメッセージが届いた場合には再送信を行うことにしました。
コンピュータ同士が接続を行うには、IPアドレスが必要です。IPアドレスとは***.***.***.***(***には0〜255までの数字が入る)の形で表されたネットワーク上での住所のようなものです。そして、そのIPアドレスには複数のコンピュータと同時に接続を行う場合に備えて、補助番号が付け加えられています。この番号のことをポートと呼びます。IPアドレスが住所なのに対して、ポートは○○○号室といった部屋番号にあたるといえます。コンピュータでは、FTPは21番、telnetは23番、HTTPは80番、のように、その役割ごとに応じて番号が決められています。
ポート番号は0〜65535番まで使う事ができますが、勝手に使って良いというわけではありません。ポートは大きく3つの種類に分ける事ができ、FTP=21のように既に定められていてるポート、予約されているポート、好きに使って良いポートに分類することができます。
よく使用されるポート | 予約済みポート | プライベートポート |
0〜1023 | 1024〜49151 | 49152〜65535 |
一般ユーザが使用できるポートは49152〜65535番までのプライベートポートですので、Speedでは50001番を使用することにしました。
これでお互いのコンピュータがメッセージのやり取りを行うことができるようになりました。次にお互いがどのようにカードの操作や、データのやりとりをするのかのメッセージの内容を決める必要があります
INVISIBLE * カード情報を更新するが、画面は更新させない UPDATE * カード情報を更新し、画面に反映させる PAUSE ゲームを止める SHUTDOWN 強制終了 OK 了承した場合のメッセージ NG 了承できなかった場合のメッセージ
スペード | S1, S2, S3, 〜 ,SA, SB, SC, SD |
ハート | H1, H2, H3, 〜 ,HA, HB, HC, SD |
クラブ | C1, C2, C3, 〜 ,CA, CB, CC, CD |
ダイヤ | D1, D2, D3, 〜 ,DA, DB, DC, DD |
ジョーカー | JJ |
なお、カード情報は
下記のように、自分の山 自分の手札 場 相手の手札 相手の山の順番で間にスペースを入れて記していきます。
↓場のカード ↓相手の山
H5C4D1S5H3H9D6DCC1C4C5C6 D1JJD9DC CACB D1JJD9DC H5C4D1S5H3H9D6DCC1C4C5C6
↑自分の山 ↑自分の手札 ↑相手の手札
メッセージ実例
# UPDATE 34603 5 H5C4D1S5H3H9D6DCC1C4C5C6 D1JJD9DC CACB D1JJD9DC H5C4D1S5H3H9D6DCC1C4C5C6
翻訳:UPDATE(カード情報を更新しなさい)
このメッセージはゲームが開始して、34.603[ms]に送信された5番目のメッセージで、
カード情報は H5(ハートの5)、C4(クラブの4)・・・C6(クラブの6)です。
という意味になります。
メッセージのやり取りの例を記します。ゲームはメッセージによっては中断されたり、強制的に終了されたりします。そのため、現在ゲームがどの状態にあるのかを、知る必要があるので、それをフラグとして表すことにしました。プログラムではゲームを管理するクラスの中のGameInfoという変数に、次のような値を持たせます。
値 名称 意味
==================================================================================
1 NOTHING ゲームが開始されていない
2 CON_WAIT 接続待ち
3 CONNECT 接続され、待機中
4 MEASURE 速度計測中
5 PLAY プレイ中
6 PAUSE_0 ゲームが一時停止していて、どちらも開始しようとしていない状態
7 PAUSE_1 ゲームが一時停止していて、どちらかがゲームを開始しようとしている状態
8 WIN 勝ち
9 DRAW 引き分け
10 DEFEAT 負け
11 EXTGAME プレイが終了し、開始待ち
通信例の図
プログラム開発において、様々なプログラムの分割法が試されてきましたが、最も人間の思考にあうものがオブジェクト指向です。まず、オブジェクトとは同一性・関連性がある「もの」や「概念」のことです。そしてオブジェクト指向というものは、詳細な内部処理については考えず、対象となるオブジェクトについて考えようというものです。プログラムを作成する際、人間がひとまとまりの対象物をプログラム上ではオブジェクトとしてとらえます。そして、オブジェクトに対してメッセージを送ると値が得られるようにプログラムを作成していきます。同じ性質を持つオブジェクトはいくつでも作れますが、このオブジェクトの性質を記述したものをクラスといいます。
例えば車というものはクラスになります。車はエンジンをかける、前に進む、曲がるなどの動作をすることができ、排気量、色、車重、馬力、トルクなどのデータを持っています。一度クラスを作成すれば、そこから個々のオブジェクトを作成することができます。1台目は排気量が2000ccで色が赤で…、2台目は排気量が1500ccで色が青で…、といったように、一つのクラスから様々な種類の車を作成することができ、個別にエンジンをかけたり、走らせたりすることができます。
クラスは自分だけが使用するだけでなく、クラスをライブラリ化して、他の人間が使用することもあります。その際、他の人間が見て分かりやすいように、そのクラスの設計や、クラス同士の関係を説明する図が必要になります。
オブジェクトは、いくつかのオブジェクトを組み合わせてして構成したり、他のオブジェクトの一部として構成したりすることができ、この方法をコンポジションと呼びます。そして、オブジェクトの一部に他のオブジェクトを含める関係のことを「Has A関係」または「Part Of」関係と呼びます。
Has-A 関係:
コンポジションによってできたオブジェクトは、その内部に構成要素のオブジェクトを「持っている」ように見えます。「車はエンジン持っている」という表現になります。
Part-Of 関係:
構成要素のオブジェクトは、その集合体であるオブジェクトの一部です。「エンジンは車の一部である」という表現になります。
この二つの関係はコンポジションの記号である ◇ で表すことができます。そのとき、車から見てもエンジンから見てもお互いが常に一つなので、両方に1を記述します。車とスピーカーの関係を見た場合、スピーカーから見ると車は1台ですが、車から見るとスピーカーは0〜不特定になります。この場合、車の方に0..*と記述します。
指定方法 | 意味 |
0..1 | 0または1 |
1 | 必ず1 |
0..* | 0以上 |
1..* | 1以上 |
n | 必ずn(ただしn > 1) |
0..n | 0以上n以下(ただしn > 1) |
1..n | 1以上n以下(ただしn > 1) |
ゲームの進行係であり、ユーザーからの要求(マウスでカードを動かしたり、ゲームを終了させたり)を受け、2〜4のクラスを扱いながらそれに相当する処理をする。カードが動かされた場合を見てみると、まず3の記録係から情報をもらい、動いた場所に置けるかを判断する。次に2のプロトコルを使って相手に情報を送信し、最後に4のキャンパスで画面を更新する。といった流れである。
接続、情報の受信・送信を行う。
山・場・手札の情報を保持したり、場に置けるかの判断をしたりする。
画面のメモリDCを扱う。(カード移動時は別)
画像データの読み込み・削除・描画などを行う。
画像に関する情報の集合体。自分自身へのポインタを持っていて、5でリスト化できる。
bool fLButton; // 左ボタンのフラグ 押されている:true int GameInfo; // ゲームの状態 HANDLE hConnect; // 接続スレッドのハンドル int nCardNo; // クリックされたカードの番号 POINT ptCard; // カード移動時にカードが摘まれた位置 POINT ptMouse; // カード移動時のマウスポインタの位置 DWORD thConnect; // 接続スレッド DWORD thReceive; // メッセージ受信スレッド CTimer cTimer; // タイマークラス // ゲーム進行に関わる bool FormatStartUp ( HWND ); // カードと画面を起動時の状態にセット bool NextGame ( int, bool ); // 現在のゲームを破棄し、次のゲームを開始する bool Restart ( int ); // ゲームの一時中断を抜ける // マウスの動作 bool LButtonDown ( POINT ); // 左クリック時の処理 bool LButtonUp ( POINT ); // マウスが放されたときの処理 bool MouseMove ( POINT ); // マウスが動いた時の処理 bool RButtonDown ( POINT ); // 右クリック時の処理 // その他の関数 void DrawTimer ( void ); // タイマーを描画する int ReturnGameInfo ( void ); // ゲームの状態を返す
SOCKADDR_IN srcAddr; // アドレス情報 HOSTENT* lpHost; // ホスト情報 UINT nIP; // IPアドレス UINT nProtocol; // 使用するプロトコル UINT nPort; // 使用するポート UINT nReceiveSize; // 受信するパケットの最大バイト数 UINT nTimeOut; // タイムアウト時間(秒数で指定) LRESULT Connect ( void ) // 接続 LRESULT Close ( void ); // 切断 char* ReceiveMessage ( void ); // 受信 LRESULT SendMessage ( const char* ); // 送信
int nBa[2]; // 場のカード(最大2枚) int nTefuda[2][8]; // 手札のカード(最大4枚) int nYama[2][32]; // 山のカード(最大27枚) char* Decode ( int ); // カードを数値から記号に戻す int Encode ( char* ); // カードを記号から数値に変換 void SetupDemo ( void ); // カードを起動時の状態にする void SetupCards ( void ); // カードを混ぜて配布する int Update ( char*, int ); // カードを受信メッセージを元に更新する
HWND hWnd; // ウィンドウのハンドル HDC hMemoryDC; // メモリデバイスコンテキスト HDC hSaveDC; // カードを移動する時に、そのカード以外の状態をセーブするHDC void FormatDC ( HWND ); // キャンパスを初期化する bool DrawCards ( CCards, bool, int ); // カードを描く void LoadAllImage ( void ); // ビットマップファイルを読み込む
Image* ptrFirstImage; // イメージファイル線形リストの先頭ポインタ bool Read ( PSZ, PSZ ); // イメージファイル読み込み bool Delete ( PSZ ); // イメージファイル消去 bool Draw ( HDC, PSZ, int, int ); // 描画 bool SetMask ( PSZ, COLORREF ); // 透過色を指定
HDC hDC; // デバイスコンテキスト COLORREF Maskcolor; // 透過色 CHAR szFilePass[lenStrDef]; // イメージファイルへのパス