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) 裏返しアニメーションを付けてついに完成!