2.ソフトウェア設計

2−1.スピードのルール

1.はじめに

スピードはトランプのゲームです。ここではスピードのルールを説明しますが、そのルールは地方によって様々であったり、オプションのように付け加える事ができるルールがあったりするようです。その点に関しては、今回は特にこだわる必要がないので、ジョーカー2枚を含めるルールの中で最もシンプルなルールを適用しました。

2.必須事項と、用語説明

2-1-2

3.ゲームを始める前の準備

  1. 全てのカードを黒系(スペード・クラブ)と赤系(ハート・ダイヤ)の二つに分ける。ジョーカーは黒系と赤系に1枚づつ配る。
  2. 各プレイヤーが黒毛と、赤系のカードのどちらか好きな方を取り、自分の手元に裏返して置く。これが山に相当する。
  3. 各プレイヤーが山からカードを4枚づつ取り、表にして手元に置く。これが手札に相当する。
  4. 各プレイヤーが自分の山のカードから一枚づつ手に持ち、このカードを同タイミングで、場に表向きに置いた時点でゲームが開始される。

4.ゲームの進行

  1. 場に置かれた2枚のカードを見て、両方のカードの数字を+1か−1する。ただし、Kを+1すると1になり、1を−1するとKになる。この際にカードの種類は問わない。図では場にダイヤの1s-d1が置いてあるのでこれに対して、Ks-sdと2s-s2の数字が書いてあるカードが相当し、スペードの5s-s5が場においてあるのでこれに対して、4s-s4と6s-s6の数字が書いてあるカードが相当する。

  2. もし1の条件に相当するカードが手札にあった場合、それを場に重ねて置いてよい。

    2枚のジョーカーs-j1は例外として、全ての種類のカードに対しておく事ができる。また、場にジョーカーが置いてあった場合は、その上に全ての種類のカードを重ねる事ができる。

    2-1-4

  3. ゲームの進行中、手札のカードが3枚以下になったとき、いつでも自分の山のカードから補充してよい。ただし手札のカードは4枚が最大枚数であるので、これを超えて補充できない。

  4. 山・手札が残っている状態で、両者とも場にカードが置けなくなった場合、ゲーム開始のときと同様に、お互いの山カードから一枚づつ手に持ち、このカードを同タイミングで場に置いた時点でゲームが再開される。このとき、山カードがない場合は手札から出してよい。

  5. 1〜4を山と手札のカードの両方が無くなるまで続け、先に無くなった方が勝ちとなる。

2−2.作成したプログラムの説明

1.Speedについて

はじめに本プログラムを「Speed」と呼び、開発をはじめました。トランプゲームのスピードはその名の通り、スピードを競うものです。プログラムでは54種類のカードを分かりやすく表示し、かつそのカードが操作しやすくなくてはなりません。よって、カードを画像として表示し、それをマウスで操作できるようにしたいと考えました。この事が容易に実現できるものがWindowsのGUIというものです。Windows環境のプログラミング言語には、Microsoft社の「Visual C++」「Visual Basic」、Borland社の「C++ Builder」等が挙げられますが、最も詳細にプログラムが作成でき、作者も使い慣れている「Visual C++ 6.0 」を選択しました。

また2台のコンピュータがネットワーク通信をして、スピードを対戦できるよう、通信プロトコルを使用します。プロトコルには主にTCPとUDPがあり、正確性には劣るが速度が早いUDPを使用して作成しました。

プログラムとプロトコルについては、順に説明していきます。それでは作成したプログラムの使い方を、使用例をもとに説明します。

2.Speedの使い方

Speedを実行すると次のような画面が現れます。

2-2-2-open

主な機能の紹介

  1. メニュー
  2. メッセージ確認用EditBox
  3. カード設置部
  4. タイマー
  1. メニュー

    メニューの種類は大まかに分けて、「ゲーム」、「設定」、「ヘルプ」に分類しました。ゲームのメニューにはアプリケーションの動作に関わるものを入れ、設定のメニューにはiniファイルの設定を変更するものを入れ、ヘルプのポップアップメニューには使い方やバージョン情報などを入れました。

    2-2-2-menu
    1. 接続開始
      2-2-2-connect

      iniファイルでIPアドレスとポート番号が設定されている場合、相手との接続を始めます。接続手順は、まずはクライアントとして接続できるかを試みます。接続できればそのまま通信開始となりますが、接続できない場合、サーバとなりクライアント接続を待ちます。このとき、時間を計測し、設定している待機時間を超えてしまった場合は接続失敗として終了します。時間内にクライアントから接続があれば、通信開始となります。メッセージの受送信の関数はサーバかクライアントかによって異なるので、どちらで開始したのかを覚えておかなければなりません。Speedでは変数で記憶されています。使用される関数

      クライアント:send() recv()

      サーバ   :sendto() recvfrom()

    2. ネットワーク調査

      ネットワークの実験を行うために作成したもので、通信が開始された後に使用することができます。指定したバイトのパケットを指定した回数送信して、その往復にかかった時間を算出するものです。パケットの送り方は複数あり、 2-2-2-packetsizeバイトを1セットとし繰り返し送信するものと、バイト数は固定として繰り返し送信する2つの方法です。どちらかのエディットボックスに任意の値を入力して送信すると結果のダイアログボックスが出てきます。

      2-2-2-netresearch
      2のx乗バイトを1セット送信した場合 1500バイトを13回送信した場合

      2-2-2-nr01

      2-2-2-nr02

    3. ゲーム設定

      setup.iniの設定を行うメニューです。ゲーム設定ではゲームの地方ルールなどの設定をしたかったのですが、今回は実装には至りませんでしたので省略します。

      2-2-2-gameconfig
    4. ネットワーク設定

      2-2-2-netconfig

      対戦者IPアドレス IPアドレスと、ホスト名の両方に対応
      対戦者を待つ時間 接続を試みてから、相手からの応答がなかった場合に、接続をやめるまでのタイムアウト時間を設定します。
      使用ポート プライベートポート(ユーザが好きなように使えるポート)を使用します。通常であれば50001番が元からデフォルト値で入っています。別のポート番号を使用したければ、変更しても構いませんが、対戦相手も同じ番号を使用する必要があります。ウイルス駆除ソフト等をインストールしている場合は、ファイアーウォールの設定で、50001番ポートを通るようにする必要があります。
  2. メッセージ確認用 EditBox

    ゲームが開始されてからのお互いの通信内容が記述されていく部分です。メッセージのバイト数が大きい場合は画面には表示しきれないので、スクロールバーで移動してメッセージを確認します。これにより、いつ、どんなメッセージが受信されたか、送信されたかが分かるようになります。

    2-2-2-editbox

    詳しくは3−1の2で説明がありますのでそちらをご覧下さい。

  3. 0カード設置部

    下の図を説明すると、左下に裏側になっているカードが山、下の段に表側になっているカードが手札、上の段に2枚あるカードが場です。カードの操作は全てマウスで行います。ゲーム中の操作は、

    1.手札のカードを場に置く

    2.場のカードを手札に置く

    この2種類のみです。両方とも操作方法は同じで、基本的に「つまむ」「移動する」「離す」の3つの動作をマウスで行います。まず「つまむ」には、マウスポインタをカードの画像上に持っていき、左ボタンを押します。このとき左ボタンは押したままにしてください。「移動する」にはつかんだカードを置きたい場所までドラッグします。最後に「離す」とき、押しっぱなしになっているマウスの左ボタンを離して完了です。

    もしマウスの左ボタンを離すときに、カードの画像上ではないところで離してしまった場合は、動作が無効になります。なお移動に成功するとカードは消えますが、下の図のように点線の囲いができますので、山から手札にカードを持ってくるときは、点線を目安にしてください。

    2-2-2-cardset
  4. タイマー

    ゲーム開始からの経過時間を随時表示されます。タイマーの詳詳細は3−1の1で説明がありますのでそちらをご覧下さい。

    2-2-2-timer

2−3.ネットワークプロトコル

1.ネットワークのしくみ

今回のプログラムで最も欠かせない部分が、ネットワーク通信です。コンピュータをはじめとするや情報機器が通信を行うには、多くの手順を必要とします。その手順の役割を7つの層に分類したものが、「OSI参照モデル」といいます。私達が電話をかける際に、どのような手順で通信しているのかを知らなくても、電話の使い方さえ知っていれば通信ができるのと同じで、手順を分類する事によって、各層は全ての動作を知らなくても、すぐ一つ下の層の使い方さえ知っていれば、通信ができるという事になります。その役割は次の通りです。

物理層 通信回路に0か1かの電気的な信号を送信する
データリンク層 通信相手との物理的な通信路を確保し、通信路に流れるデータのエラー検出などを行う
ネットワーク層 相手にデータを届けるための通信経路の選択や、通信経路内のアドレスを管理する
トランスポート層 相手まで確実に、または効率よくデータを届けるためのデータ圧縮や、誤り訂正や、再送信要求などを行う
セッション層 プログラム同士がデータの送受信を行うための仮想的な経路の確立や開放を行う
プレゼンテーション層 セッション層から受け取ったデータを、ユーザに分かりやすい形に変換したり、アプリケーション層から送られてくるデータを通信に適した形式に変換する
アプリケーション層 ユーザの操作により、プログラムが接続処理を行う、いわばプログラム間の通信

2.プロトコルとは

ネットワークを利用するコンピュータはWindowsに限ったことではありません。MacやLinuxやUNIXなど、異なる種類のOSが接続する可能性があります。またPCのみならず、携帯電話や家電製品からの接続があるかもしれません。様々な種類の機器を通信させる場合には、どのような手順で通信をするのかという約束事を定める必要があります。その約束事の集まりのことをプロトコルと呼び、物理層以外の全てに存在します。

トランスポート層のプロトコルで代表的なものにTCP(Transmission Control Protocol)と、UDP(User Datagram Protocol)の二つが挙げられます。現在、ほとんどの通信に使われているプロトコルがTCPであり、私達がホームページの閲覧する際に使用するHTTP、サーバの特定の場所ににファイルをアップロード・ダウンロードするFTP、メールの送受信を行うSMTP、POPなどは、全てTCPで行われています。一方UDPの方は、LAN内で利用されるアプリケーションや、共有ファイルの送受信や、ネットワークでコンピュータの時計を合わせるソフトウェアなどに使用されているようです。TCPとUDPの特性は異なるので、その使用用途によってプロトコルを選択する必要があります。

3.TCPとUDPの特徴

TCPの特徴 UDPの特徴
エラー訂正・再送信処理がある 送ったデータが相手に届いたか保障されない
一度に大量のデータを送信できる 一度に大量のデータを送信できない
通信速度が遅い 通信速度が速い

TCPは時間がかかるが、相手に確実にデータを送信する事ができ、UDPは送信時間が速いが、相手に確実にデータが送られているか分からない、ということが言えます。今回作成するSpeedは一回のメッセージが最大でも256[Byte]を越える事はない小さいサイズであるということと、速度を重視するという点で、UDPを選択しています。その代わりに、パケットの通信中に解読不明なメッセージが来たり、一部欠けてしまっているメッセージが来た場合には、切り捨ててしまうか、再送信を行わなければなりません。この点に関しては、ゲーム中カードは常に1枚しか移動する事ができないという事を利用して、複数のカードが更新されたり、解読不能な命令文のメッセージが届いた場合には再送信を行うことにしました。

4.ポート番号

コンピュータ同士が接続を行うには、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番を使用することにしました。

これでお互いのコンピュータがメッセージのやり取りを行うことができるようになりました。次にお互いがどのようにカードの操作や、データのやりとりをするのかのメッセージの内容を決める必要があります

5.Speedが送受信するメッセージの説明

  1. Speedが送受信するメッセージは次のように定めました。
    1. 先頭に#をつける
    2. 次に命令文を記す
    3. 次に、接続が開始してから何ミリ秒たったかの時間を記す
    4. 次に接続が開始してから何番目のメッセージなのかを記す
    5. 命令文にカード情報などのオプションが必要であれば、記す
    6. 1〜5の間には全て半角スペースを入れる
  2. 命令の詳細(*にはカード情報が入る)
    INVISIBLE *   カード情報を更新するが、画面は更新させない
    UPDATE *     カード情報を更新し、画面に反映させる
    PAUSE      ゲームを止める
    SHUTDOWN     強制終了
    OK        了承した場合のメッセージ
    NG        了承できなかった場合のメッセージ
  3. カード情報は全て2バイトの文字で表されます。また、カードが無い状態はNLとします。
    スペード 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)です。
    という意味になります。

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  プレイが終了し、開始待ち

通信例の図

2-3-6

2−4.クラスの説明

1.オブジェクト指向とは

プログラム開発において、様々なプログラムの分割法が試されてきましたが、最も人間の思考にあうものがオブジェクト指向です。まず、オブジェクトとは同一性・関連性がある「もの」や「概念」のことです。そしてオブジェクト指向というものは、詳細な内部処理については考えず、対象となるオブジェクトについて考えようというものです。プログラムを作成する際、人間がひとまとまりの対象物をプログラム上ではオブジェクトとしてとらえます。そして、オブジェクトに対してメッセージを送ると値が得られるようにプログラムを作成していきます。同じ性質を持つオブジェクトはいくつでも作れますが、このオブジェクトの性質を記述したものをクラスといいます。

例えば車というものはクラスになります。車はエンジンをかける、前に進む、曲がるなどの動作をすることができ、排気量、色、車重、馬力、トルクなどのデータを持っています。一度クラスを作成すれば、そこから個々のオブジェクトを作成することができます。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-1

2.スピードを行う際に必要となるクラス

  1. ゲームのルールブックとなるクラス(ディーラー)

    ゲームの進行係であり、ユーザーからの要求(マウスでカードを動かしたり、ゲームを終了させたり)を受け、2〜4のクラスを扱いながらそれに相当する処理をする。カードが動かされた場合を見てみると、まず3の記録係から情報をもらい、動いた場所に置けるかを判断する。次に2のプロトコルを使って相手に情報を送信し、最後に4のキャンパスで画面を更新する。といった流れである。

  2. ネットワーク接続を行い、情報のやり取りをするクラス(ソケット)

    接続、情報の受信・送信を行う。

  3. カード情報を保持するクラス(記録係)

    山・場・手札の情報を保持したり、場に置けるかの判断をしたりする。

  4. 画面に描画をするクラス(キャンパス)

    画面のメモリDCを扱う。(カード移動時は別)

  5. 全ての画像ファイルをまとめるクラス(アルバム)

    画像データの読み込み・削除・描画などを行う。

  6. 画像を操作する構造体(写真)

    画像に関する情報の集合体。自分自身へのポインタを持っていて、5でリスト化できる。

3.クラスの主なメンバの紹介

  1. ディーラークラス「CGame」の主なメンバ
    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 );    // ゲームの状態を返す
  2. ソケットを操作するクラス「CSocket」の主なメンバ
    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* );    // 送信
  3. カード情報の記録係クラス「CCards」の主なメンバ
    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 );  // カードを受信メッセージを元に更新する
  4. キャンパスのクラス「CDraw」の主なメンバ
    HWND    hWnd;       // ウィンドウのハンドル
    HDC     hMemoryDC;  // メモリデバイスコンテキスト
    HDC     hSaveDC;    // カードを移動する時に、そのカード以外の状態をセーブするHDC
    
    void    FormatDC ( HWND );                  // キャンパスを初期化する
    bool    DrawCards ( CCards, bool, int );    // カードを描く
    void    LoadAllImage ( void );              // ビットマップファイルを読み込む
  5. アルバムのクラス「CImage」の主なメンバ
    Image*  ptrFirstImage;                  // イメージファイル線形リストの先頭ポインタ
    
    bool    Read ( PSZ, PSZ );              // イメージファイル読み込み
    bool    Delete ( PSZ  );                // イメージファイル消去
    bool    Draw ( HDC, PSZ, int, int );    // 描画
    bool    SetMask ( PSZ, COLORREF );      // 透過色を指定
  6. 写真の構造体「Image」の主なメンバ
    HDC         hDC;                    // デバイスコンテキスト
    COLORREF    Maskcolor;              // 透過色
    CHAR        szFilePass[lenStrDef];  // イメージファイルへのパス

2−5.クラス間の関係

2-2-5

top top next