TopPage > 最終課題に向けて > 演習4-2 オセロの基礎2

オセロの基礎2

コマを置ける条件

  • オセロのコマを置ける条件についてまとめてみます.
  1. 自分のターンである
  2. 置く場所にコマがない
  3. 置くと,必ず相手のコマが1コ以上裏返る
  • 条件1は演習4-1で,できました.やったー.
  • 条件2も,押したボタンがboardIconかどうかを判断するだけなので,すぐできますね.追加しましょう.
  • 条件3のプログラムは,色々なアルゴリズムが考えられると思います.
    • 難しいと思う人は,ここでその一例をご紹介します.
    • できそうな部分で参考をやめて,自分で書いていくのが良いですよ.

課題4・隣8方向の判定

  • それでは,課題4・課題5を通して,条件3を追加してみましょう.
    • 押されたボタンがboardIconだと分かった後に,theArrayIndexをint型に変換します.
    • その数値をy成分とx成分に分解し(演習4-1参照),以下のように書いてください.
      if(judgeButton(y, x)){
        //置ける
        PLACE命令の送信
      } else {
        //置けない
        System.out.println("そこには配置できません");
      }
  • judgeButtonは,これから新しく作る関数の名前です.
関数 judgeButton
裏返りの発生するボタンであるかどうかを判定する
引数1 y ボタンのy位置(0-7)
引数2 x ボタンのx位置(0-7)
返り値 判定(TF)
  • この関数の返り値はBoolean型です.指定した位置について,コマを置けたらtrue,置けないならfalseを返す関数です.
  • では,mouseMovedの下に,新しい関数を追加します.
    public boolean judgeButton(int y, int x){
      boolean flag = false;
      
      //色々な条件からflagをtrueにするか判断する
      
      return flag;
    }
  • flagをtrueにする手順は複雑ですが,まずはこのような条件を決めてみましょう.
    1. 隣8方向に相手のコマがあるか,ひとつずつ見ていく
    2. ひとつでもあれば,flagをtrueにする
  • ここで,隣8方向の参照のしかたを下図に示します.
direction.png
図1. 隣8方向の求め方
  • buttonArray[y][x]を用いると,クリックされたボタンの情報を取得できます.
    • このy,xをそれぞれ±1することで,周りのボタンの情報も取ることができますね.
  • 効率の良いプログラムを作成するには,指定の位置を(0,0)とおき,iとjを用いた相対的な座標を考えます.
    • iとjの二重for文から,自分(j=0,i=0)以外のまわり8コマを順に見ていきます.
    • for文の中で,buttonArray[y+j][x+i]のアイコンがyourIconならばflagをtrueにします.
  • この段階で,メッセージを出しながらデバッグ(テスト)を行ってください.
    • 相手のコマがまわりのどこかにある時に,自分のコマが置けるようになっていますか?
  • なお,端っこのボタンにコマを置こうとすると,エラーが発生すると思います.
    • 8方向の範囲に場外を含むことで,配列が-1や8を参照してしまうためです.
    • この問題は,あとで対応するとします.

課題5・ひっくり返す判定

  • いよいよ,挟まれた相手のコマをひっくり返す判定に移ります.
  • 課題4の「アイコンがyourIconなら」という条件を,「flipButtons()の返り値が1以上なら」という条件に変えてください.
  • flipButtonsは今から作る新しい関数です.仮引数にy,x,j,iの4つを与えましょう.
関数 flipButtons
一方向にあるコマ群を裏返す命令を送る
引数1 y ボタンのy位置(0-7)
引数2 x ボタンのx位置(0-7)
引数3 j y成分ベクトル(-1,0,1)
引数4 i x成分ベクトル(-1,0,1)
返り値 裏返ったコマの数
  • 返り値はint型です.この関数は,指定した位置と方向について,ひっくり返せるコマの個数を返します.
  • flipButtonsの中身では,flagのような返り値用の変数flipNum = 0;も用意しておきます.
 
  • さて,相手のコマが何個ひっくり返るかを見るには,同じ方向に何個連鎖(連続)してひっくり返るかを調べなければなりません.
    • flipButtonsには,基準となるボタンの絶対位置(y,x)と,ひっくり返る可能性のある方向成分(j,i)を渡します.
    • これらの情報があれば,どこからどの方向に調べていくかが分かりますね.
flipothello.png
図2. 隣8方向の先の求め方
  • 隣2マス以上に離れたボタンを調べるには,j,iの値を使います.j,iを,方向の単位ベクトルとして用いましょう
    • (1, 0)→(2, 0)→(3, 0)…
    • (-1,-1)→(-2, -2)→(-3, -3)…
    • 上記を見ると,どの方向も,j,iを足し続けることで遠いボタンの位置を把握できそうです.
 
  • それでは,変数dy,dxを用意して,固定値j,iを足し続けるようなループ文を作りましょう.
    • y+dyの値が,調べたいボタンの絶対位置になるようにします.
  • というわけで,少し変わったfor文の書き方をしてみます.どんな処理か考えてください.
    for(int dy=j, dx=i; ; dy+=j, dx+=i) {
      ...
    }
  • わかりました?
    • for文は,「初期化式; 継続条件式; 再初期化式」の順に書きます.
    • 「2つの変数について初期化できる点」「これらの式は省略できる点」がポイントです.
    • ただし,このままでは永遠に継続してしまうので,どこかでbreak文を使う必要があります.
  • このfor文の中身は,以下のようになっていればOKです.
  1. y+dy,x+dxの位置が場外なら,この関数は0を返す(判定終了)
  2. この位置のアイコンを取得する
  3. アイコンの種類を見る
    1. boardIconなら,この関数は0を返す(判定終了)
    2. myIconなら,この関数はflipNumを返す(連鎖ストップ)
    3. yourIconなら,flipNumを1増やす(連鎖が続く)
  • ちょっと説明します.
    • 1方向に突き進んでいき,連続して相手のコマがある限りは,まだ裏返せる可能性がありますよね.(flipNumを増やし続ける)
    • その道中,自分のコマにぶつかったら,「return flipNum」でさっさとfor文どころか関数を抜けます.
    • ただし,その道中にboardIconがあったり,自分のコマが見つからないまま場外にはみ出たりした場合も,さっさと0を返しましょう.
 
  • 実際のひっくり返す命令は,flipNumが1以上の時に,以下のようなループ文を回して書いていきます.
for(int dy=j, dx=i, k=0; k<flipNum; k++, dx+=j, dy+=i){
  //ボタンの位置情報を作る
  int msgy = y + dy;
  int msgx = x + dx;
  int theArrayIndex = msgy*8 + msgx;
  
  //サーバに情報を送る
  String msg = "FLIP"+" "+theArrayIndex+" "+myColor;
  out.println(msg);
  out.flush();
}
  • judgeButton,flipButtons内のどちらかに追記しましょう.後者の場合は,連鎖ストップ時にbreak文で抜けます.
  • あとは,FLIP命令の受信部分を,PLACEのように書くだけです.

課題6・ゲームの終了と勝敗判定

  • オセロの公式ルールでは,自分のターンで必ず相手のコマをひっくり返します.
    • どこに置いてもひっくり返せない場合に,ターンのパスができます.パスに回数制限はありません.
    • ゲームの終了条件は,全てにコマが埋まるか,両者とも置けなくなった場合です.
  • 自動でパスを発生させるか,いつでもパスができるボタンを作るか.どちらか選んでください.
 
  • 「これ以上コマを置けなくなった」の自動判定は,PLACEするたびに各boardIconのボタンの状況を探りましょう.
    • 両者とも置けなくなった時点で,各色のコマ数を数えて勝敗を決めます.
  • パスボタンを作る場合,本課題では特別に,「お互いにパスが連続したら勝敗の判定に移る」というルールを加えてもOKとします.
    • ただし,「全てのコマが埋まったらすぐに勝敗判定に移る」機能は付けてくださいね.
  • 最後に,ボタンのマウスリスナーを削除して,クリックを無効化する方法を教えます.
    MouseListener[] listeners = button.getMouseListeners();
    for (MouseListener listener : listeners) {
      button.removeMouseListener(listener);
    }
    • ちょっと面倒!と感じたら,上記の関数化にチャレンジしてみてくださいね.

おわりに

  • ここまでできたら,とりあえず提出できる自動オセロとみなします!
    • 提出作品とする場合は,必ず画像を自分で用意したものにしてください
    • ここから新機能を追加したり,ここで得た知識からほかのゲームの作成を目指せば,高得点につながります(たぶん).
    • そのほか,詳細の最終課題ページをよく読んで,余裕のある課題作りを心掛けましょう!
 

next.gif 最終課題へ


添付ファイル: filedirection.png 681件 [詳細] fileflipothello.png 671件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   一覧 単語検索 最終更新     最終更新のRSS
Last-modified: 2016-08-31 (水) 06:18:42 (2785d)