2011年7月14日

Androidでオセロゲームを作ってみる (2) ゲームロジックの実装

Androidでオセロゲームを作ってみる (1)
Androidでオセロゲームを作ってみる (2) ゲームロジックの実装
Androidでオセロゲームを作ってみる (3) 思考ルーチンの実装
Androidでオセロゲームを作ってみる (4) 文字列をぐるぐる回す方法
Androidでオセロゲームを作ってみる (5) 裏返しアニメーションを付けてついに完成!


前回はひとまずカスタムViewを使ってボードを描いて、画面をタッチした時に石を配置する所まで出来た。

今回はゲームのロジックを実装して実際に遊べる所まで一通り作ってみた。

必要な処理は次の4つになる。
  1. 画面タッチ時に、押された場所が石を配置しても良い場所かどうか判断する。
  2. 挟んだ相手の石を自分の石で置き換える。
  3. 配置可能な場所が無い場合はスキップする。
  4. 両者とも配置可能な場所が無い場合はゲーム終了とする。
これに加えて、「次に配置可能な場所」を示してくれるヒント機能も付けて見た。

小さな丸で表示されているのが「次に配置可能な場所」。
濃い色にすると見辛くなるのでかなり薄くしてある。


ゲーム終了時の結果表示。とりあえずToastでさらっと。


メニューは「設定」、「カウント」、「最初から始める」、「閉じる」の4つ。
設定でヒント表示のON/OFFを変更出来る。


画面を縦表示に固定


マーケットで配布されているオセロゲームをいくつか試した限りでは、全て縦表示で固定されていたので、同じように縦表示で固定する事にした。これはマニフェストファイルでの設定だけなので簡単だ。
Activityタグの属性として、
android:screenOrientation="portrait"
を入れておけばOK。

画面が切り替わる時に盤面の状態を保存


何もしないと、一旦他の画面に切り替わった後で再表示される場合に画面が初期状態に戻ってしまう。

なので盤面の状態を保存しないと行けない。

そのために、Boardクラスに getStateString というメソッドを作った。ここから各セルのステータスを1文字で表したものを連結した64文字からなる文字列を返す様にする。さらに現在どちらの番なのかを表す文字を先頭に付加するので、実際には1+64=65文字になる。

本当の所は、セルの状態は「黒/白/空白」の3つしか無いので情報としては2ビットあれば足りる。そう考えると2bit x 64セル / 8 + 1 = 17という事で、17バイトのバイト配列に格納する事も出来る。ただビット演算などが面倒なので今はこのまま65文字の文字列に格納する事にする。

最初は、この文字列を ViewのonSaveInstanceStateで状態を保存して、onRestoreInstanceStateで復元していた。

ただこれだとひとつ問題がある。端末の「戻る」ボタンで画面を閉じた時にはどちらのメソッドも呼ばれず、状態が失われてしまうのだ。

なのでこれらのメソッドを使わずに代わりにActivityのonPauseとonResumeを使う様に変更した。この場合はデータの保存先はプログラムで適当な所を選ぶ必要がある。今回は保存したいデータが65文字の文字列なので、SharedPreferenceを使う事にした。

これで「戻る」ボタンが押されてもちゃんと状態が保存される様になった。




Viewの invalidate メソッドで必要な部分のみを再描画


画面がタッチされて石を配置する時に BoardクラスのchangeCellメソッドを呼んでいる。このメソッドはこの配置によって変更された全てのセルをArrayListに入れて返してくれる。View側ではこれを受け取って、順に変更されたセルの領域を指定しながらViewのinvalidateメソッドを呼んでいる。

invalidateを引数なしで呼んだ場合はView全体が再描画されるが、引数rectに領域を指定して呼び出すと、その領域だけが再描画される。もちろん出来るだけ再描画する量を減らした方が処理が速くなる。



drawTextで文字を表示


画面の下の部分に現在どちらの番なのかが分かるように drawRoundRectメソッドで四角の枠を表示した。それから、黒と白それぞれの現在の数をdrawTextメソッドで表示する様にした。

さて、これらの座標やフォントサイズの指定方法に少し悩んだ。

基本的には、何も考えずに数値を指定すれば Android側がDIPというデバイス非依存な単位として扱ってくれるらしい。だから画面の物理的な大きさや解像度が変わってもプログラム側は何もしなくてもいいはずだ。

でもこのページを読むと、なにやらXperiaでちょっとした問題があるとの事。
Y.A.M の 雑記帳: Android multi screen 対応
ただし、Xperia では anyDensity = false にすると、drawable-hdpi ではなく drawable-mdpi に格納した画像が使われるという問題があります。

ということで、私は anyDensity = true にして、XML側で sp や dip でサイズ指定し、コード側で指定しなければならない場合は、XMLで dimension を設定して、それを getDimension してsetWidth などに渡しています。
という事なので、yanzmさんの真似をしてXMLファイルに座標関係の値を全て書き出す事にした。

XMLファイルの書き方とプログラムでの使い方の説明はこちら。
Y.A.M の 雑記帳: Android Dimension 単位

いちいちXMLを編集するのは最初はちょっと面倒だけれども、慣れてしまえば逆に微妙な調整などはこの方がやり易いかも知れない。

デバイス間の解像度の違いについては、実機(800x480)とエミュレータ(480x320)で試して見たところ上手く表示されているので、多分大丈夫。


ゲームロジックの実装


Cellクラスで表されている各セルは「今このセルに石が配置されたらどのセルとどのセルが裏返されるか」という「裏返し対象セル」のリスト(mReversibleCells)を持っている。手番が変わるごとに毎回全てのセルについてこれを計算し直している。

このリストのサイズが1以上ある場合は、そのセルは次に石を配置可能な場所であるという事になる。サイズが0なら、タッチされても何も起こらない。

リストのサイズが1以上ある(配置可能な)セルがタッチされた場合は、この前もって計算された「裏返し対象リスト」に格納されているセルのステータスを順に現在の手番を表すステータス(黒または白)に置き換えて行く。

もし配置可能なセルが一つも存在しない状態になった場合は、その手番はスキップ(パス)される。




今回はここまで


さて、これで一応ゲーム開始から終了まで動作する状態になった。

でもここまで来ると、もう少し完成度を上げて見たくなる。やっぱり、石はきれいな画像で表示したいし、裏返す時のアニメーション効果とコンピュータとの対戦機能ぐらいは欲しい。

どこかにそこそこ強いオセロの思考ルーチンのコードが公開されていないものだろうか。探して見よう。(笑)

ここまでのプロジェクト全体のダウンロードはこちらから。
mikehibm/MiReversi at ver2 - GitHub


Androidでオセロゲームを作ってみる (1)
Androidでオセロゲームを作ってみる (2) ゲームロジックの実装
Androidでオセロゲームを作ってみる (3) 思考ルーチンの実装
Androidでオセロゲームを作ってみる (4) 文字列をぐるぐる回す方法
Androidでオセロゲームを作ってみる (5) 裏返しアニメーションを付けてついに完成!









 

2011年6月29日

Androidでオセロゲームを作ってみる (1)

Androidでオセロゲームを作ってみる (1)
Androidでオセロゲームを作ってみる (2) ゲームロジックの実装
Androidでオセロゲームを作ってみる (3) 思考ルーチンの実装
Androidでオセロゲームを作ってみる (4) 文字列をぐるぐる回す方法
Androidでオセロゲームを作ってみる (5) 裏返しアニメーションを付けてついに完成!


Androidアプリで2Dグラフィックスを扱う練習をしようと思う。

とりあえず簡単なものという事で、オセロ(リバーシ)ゲームを作りたい。

まずはViewを使って描画するか、SurfaceViewを使って描画するかを決めないと行けない。
GLSurfaceViewというのもあるけれどもこちらは今やろうとしている事にはオーバースペックなので置いておく。

SurfaceViewならAndroidで高速描画ゲームが作れる (1/3) - @IT

【libro】 Google androidプログラミング入門/SurfaceViewによる高速描画/SurfaceViewとは?

Graphics の基本

グラフィックス(1)-Viewクラスへの描画 - 愚鈍人

KENSINのホームページ-Androidで星が流れるプログラムを作成してみる

Viewを使った場合は大体4fpsぐらいしか出ないが、SurfaceViewだと30fpsぐらいは出るみたいだ。この差は大きい。

でも「チェスなどのボードゲームならViewで充分」という記述もよく見かけるので、今回はとりあえずViewで行こうと思う。どうしても必要なら後からSurfaceViewに変更するという事で。

まずはこんな感じで初期状態を描画してみた。それなりに見える。(笑)

横向きの場合はこんな感じになる。




カスタムビューによるグラフィック表示


Activityクラスではカスタムビューをセットするだけ。


ReversiViewというカスタムビューを作ってこの中でオセロの表示処理を全て行う。


onDrawから呼ばれるdrawBoardの中で
Paint paint = new Paint();
としているのはパフォーマンス上は良くないらしい。後でちゃんと直そうと思う。

色の指定なども本当はXMLファイルに出してしまいたいところ。

あと、円を描く前にアンチエイリアスを指定しないとギザギザになってしまうので注意。


モデルとビューの分離


オセロの盤面の状態を保持するのと、ゲームのロジックを記述するために2つのクラスを作成しておこう。盤面全体を表すBoardクラスと、個々のマスを表すCellクラスがあれば良いと思う。

Boardクラスにはwidth, height, top, leftプロパティと、Cellクラスのオブジェクトを格納する2次元配列を用意しておく。

Cellクラスには、width, height, top, leftプロパティと、そのマスがどういう状態にあるかを示す変数を作成しておこう。各マスは、「何もない」「黒」「白」の3つの状態を持つので、それらを表すEnum型も定義する。

Boardクラスのソースコードはこちら。

Cellクラスのソースコードはこちら。


とりあえずここまで


ここまでで、画面をタッチすればそこに黒または白の石を置く事が出来る様になった。

一応、既に石が置かれているマスをタッチしても反応しない様になっている。でもまだゲームのロジックと言えるものは何も書いていないので、オセロのルールに関係無く自由に石を置けてしまうし、挟んでも挟まれても何も起こらない。

しかし、これだけでも「一から自分で作っている」という感じがたまらない。(笑)

プロジェクト全体のダウンロードはこちらから。
mikehibm/MiReversi at master - GitHub


Androidでオセロゲームを作ってみる (1)
Androidでオセロゲームを作ってみる (2) ゲームロジックの実装
Androidでオセロゲームを作ってみる (3) 思考ルーチンの実装
Androidでオセロゲームを作ってみる (4) 文字列をぐるぐる回す方法
Androidでオセロゲームを作ってみる (5) 裏返しアニメーションを付けてついに完成!







2011年6月16日

AndroidでURLを開く度に自作のアプリを起動する (5) 時間のかかる処理を別スレッドで実行する



HTTP通信を別スレッド化する


Androidアプリケーションでスレッドを使う方法はいろいろあるみたいだが、今回は以下の手順で実装した。
  1. ActivityクラスでRunnableインターフェースをインプリメントする。
  2. android.os.HandlerクラスのインスタンスをActivityクラスで保持する。
  3. Runnableのrunメソッドをオーバーライドする。
  4. 別スレッドでの処理を開始する。


1. ActivityクラスでRunnableインターフェースをインプリメントする。

Activityでインプリメントせずに、単にその都度Runnableのインスタンスを生成しても多分大丈夫。ただこちらの方が多少はメモリの節約になるらしい。(という記述をどこかで読んだ気がする。)
public class IntentReceiveActivity extends Activity implements Runnable {
  ...
}

2. android.os.HandlerクラスのインスタンスをActivityクラスで保持する。

別スレッドでの処理が完了した後にUIスレッド側で実行する処理を受け取る為に必要になる。
private Handler mHandler = new Handler();

3. RunnableのrunメソッドをOverrideする。

別スレッドでの処理が完了した後、mHandlerのpost()メソッドを呼んでUIスレッド側で実行したい処理を渡す。
変数urlとtitleはfinalで宣言しないとハンドラ内から参照する事が出来ないので注意。でも逆に言うとfinalで宣言したローカル変数がそのまま参照出来るので、Bundleに詰め込んでパラメータとして渡すなどという方法と比べるとこの書き方はかなり便利だと思う。
@Override
 public void run() {
  //HTTP通信を実行してページのタイトルを取得
  final String url = getIntent().getDataString();
  final String title = HttpUtil.getTitle(url, getString(R.string.msg_no_title));

  //処理完了後、ハンドラにUIスレッド側で実行する処理を渡す。
  mHandler.post(new Runnable(){
   @Override
   public void run(){
    try {
     //ListViewを更新(該当のurlが既にListViewから削除されていた場合はfalseを返す)
     if (updateList(url, title)){
      //データベースを更新。
      HistoryDb.save(url, title);
     }
    } catch (Exception e) {
        showErrorDialog(e);
    }
   }
  });
 }

4. 別スレッドでの処理を開始する。
new Thread(this).start();
実行時のイメージはこんな感じになった。処理中はくるくる回るアイコンを表示しておくと結構いい感じ。

HTTP通信処理中の表示(一番上の行)


複数のスレッドが同時に走っているのが分かる

処理中とそうでない時の表示の切り替えは ListItemAdapter クラスの中でtitleがセットされているかどうかで判断して行っている。その部分のソースは こちら。



ネットワーク接続が可能かどうか事前に調べる


ネットワークに接続されていない場合はタイトルの取得を行わずにURLだけを一覧に追加する様にした。接続されているかどうかの判断は下の関数で可能だ。

private boolean isConnected(){
  ConnectivityManager cm = (ConnectivityManager)getSystemService(CONNECTIVITY_SERVICE);
  NetworkInfo info = cm.getActiveNetworkInfo();
  if (info == null){
   return false;
  } else {
   if (info.isConnected()){
    return true;
   } else {
    return false;
   }
  }
 }
AndroidSDK開発のレシピ―104個のレシピで学ぶAndroidアプリ開発の極意 塚田 翔也 (著)より



ブラウザを自動的に開くかどうかを設定で選べる様にする


設定画面にチェックボタンを追加して、インテントで受け取ったURLを自動的にブラウザで開くかどうか設定出来る様にした。ONの場合とOFFの場合で試してみた所、自動的にブラウザを開かずに単に一覧リストを表示するだけの方が使いやすい様な気がする。




リダイレクト時に複数のエントリが追加されてしまう問題


自動的にブラウザを開く場合にWebサーバーからリダイレクトのレスポンスが返って来ると、標準ブラウザはリダイレクト先のURLを開く為にまたインテントを発行する様になっている。そのインテントを再度このアプリが受け取ると、結局一覧リスト上はURLが微妙に違うだけで内容は同じページ、という行が複数追加されてしまう事になる。

例えば、
http://d.hatena.ne.jp/hogehoge/
というサイトを開いて、
http://d.hatena.ne.jp/hogehoge/touch
にリダイレクトされた場合にこの現象が起きる。

これを防ごうとすると、

「インテントを受け取った時にそれがリダイレクトされて来たものなのかどうか判定してそうであれば無視する。」

という動作が出来ればいいのだが、その判定方法が見つからないのでこの方法は今の所実現出来ていない。

次善の策としては、

「時間制限を設けて、前のインテント処理から一定の時間内(20秒とか)は同じタイトルの行を追加しない様にする。」

というやり方が考えられる。

ただ、ややこしくなりそうなのでそこまで実装するのは今回は見送り。(笑)



全ソースと次の目標


今回の全ソースはこちらからダウンロード可能になっている。
mikehibm/android-browser-intent03 at intent04 - GitHub

他にも「設定画面で標準以外のブラウザを選べる様にする」とか、「ListViewにチェックボックスを付けて処理対象を選択出来るようにする」とか、検討した機能はあった。

「メールを送る代わりにどこかのサーバーにデータを送信する」というのも面白そうだ。Google DocumentsのSpreadsheetに書き込めば意外と使い途があるかも知れない。

ただこのアプリを変更するのは今回で一旦ストップしておこうと思う。

実を言うと、「気になったリンクの一覧を簡単にPCに送る」というもともとの目的は Google Readerの「スターを付ける」機能を使えばほぼ達成出来る事に気付いてしまったからだ。
Googleリーダーに「後で読む」っぽい機能をつける方法 : ライフハッカー[日本版]
情報の収集、閲覧、集約は全てGoogleリーダーに任せることにした。 - iPhoneとiMacと自分と…

「スターを付ける」を使い出してからは、この自作アプリを使うのはTwitterなどGoogle Readerの管理外のURLをPCに送りたい時だけになってしまった。まあそれでも完全に用が無くなった訳ではないのでよしとしよう。


それに、他にもっと面白そうな事がたくさんある。あり過ぎて困る。

次からはSurfaceViewを使ったグラフィック処理や、SoundPoolを使って音を出す事などをやってみようかなと思っている。

あと、OpenGLにも興味を惹かれる。何か子供向けの教育アプリでも作れないものかなとこんな本を読んでみたりもしている。
OpenGLで作る Android SDKゲームプログラミング 中島 安彦 (著), 横江 宗太 (著), 株式会社パンカク (著)
簡単なものから始めて少しずつ機能を追加して行く構成になっていて、とても分りやすい。


AndroidでURLを開く度に自作のアプリを起動する