2011年8月3日

Androidでオセロゲームを作ってみる (3) 思考ルーチンの実装

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


前回の記事からあっと言う間に半月以上も経ってしまった。

その間、会社のXenServerがトラブったり、オフィスの引越し準備と大きなプロジェクトのカットオーバーが重なったりで、7月はなかなか大変な月だった。

そんな中で実はオセロの思考ルーチンを少しずつ実装したりもしていた。結局、仕事と子育てで一杯一杯なのはいつもの事なので、早朝5時起きしてでもなんとか自分の時間を作ってやって行くしかないかなあと思う。



思考ルーチンの実装


オセロの思考ルーチンを作り込む事は、このアプリを作り始めた時の「Androidでの2Dグラフィックス処理の習得」という目標からは完全に脱線している。でもどうしても誘惑に負けて、作り込まずにはいられなかった。(笑) 面白いのだから仕方がない。

下の2つのリンク先にはとてもとてもお世話になった。
-鶯教-コンピュータ・リバーシ講座

リバーシ(オセロ)プログラム (2018年現在リンク切れ)

今のところ、作成した思考ルーチン(?)は下の4種類。それぞれが ComputerPlayer クラスのサブクラスになっている。

(クラス名クリックでソースコードを表示します)
ComputerPlayer0 何も考えずに打てる場所の中からランダムに選んで打つ。
ComputerPlayer1 次に打つ場所の候補のリストを、事前に定義された評価テーブルにしたがってソートした上で、評価の最も高い場所に打つ。打った結果についての先読みはしない。
ComputerPlayer2 1手先までの局面の評価値を計算し、最も評価の高い手を選ぶ。
ComputerPlayer3 「ネガマックス法」にしたがって最大4手先までの局面の評価値を計算し、最も評価の高い手を選ぶ。


クラスの継承関係はこんな感じになっている。
  Player (人間とコンピュータの両方に共通のコードを記述)
     HumanPlayer   
     ComputerPlayer (スレッドの起動・停止など全ての思考ルーチンに共通する処理を記述)
       ComputerPlayer0 (各思考ルーチンの実装はこのレベルのクラスに記述)
       ComputerPlayer1
       ComputerPlayer2
       ComputerPlayer3

ゲーム実行時の基本的な流れとしては、HumanPlayerの番になるとボード上の有効な場所がタッチされるまで待ち、ComputerPlayerの番になると別スレッドを起動して思考ルーチンを走らせる様になっている。

思考ルーチンは戻り値としてPointを返すので、あとは人間がその座標をタッチした場合と同様の処理を行えばよい。

ただ、思考ルーチンが走っている間に電話がかかって来たり別のアプリが前面に呼び出されたりするとActivityが終了してしまう可能性がある。その場合は思考ルーチンのスレッドもきちんと終了させる必要があるので、その辺りにはちょっと気を使わないと行けない。



気になる強さは?


ComputerPlayer1と2はそれなりに遊べるとは思うけど、かなり弱い。

ComputerPlayer3は、個人的にはそこそこ強いのではないかと思う。ただめちゃくちゃ強いというほどでもない。自分で対戦して見た限りだと、5回やると4回は自分が負ける。1回ぐらいはぎりぎり勝てるという感じだ。

不思議な事に、こいつに自分が負けるとなぜか嬉しい。早起きして眠い目をこすりながらデバッグした甲斐があったというものだ。(笑)



ソースコードと次回の予定


現時点のプロジェクト全体のソースコードはこちら。
mikehibm/MiReversi at ver3 - GitHub

次回はいよいよ石を裏返す時のアニメーションとか、ゲーム終了時の結果表示画面などをグラフィック機能を駆使して作り込んでみたい。早寝早起きして頑張ろう。^^


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





2012/1/12 追記:
ここにある思考ルーチンは最強かも!
Booby Reversi Download Page

強いオセロプログラムについて







2011年7月16日

XenServerのiSCSI共有ストレージが壊れた時の復旧方法メモ

トラブルの概要


先日、社内で使っているXenServerの共有ストレージにアクセス出来なくなると言うトラブルが起こった。

使っていた共有ストレージは、Buffalo TeraStation ProのiSCSIモデル。セットアップしてから約1年半、全く何のトラブルも無く動いていた機器だったので、油断していた。

ビルディングのメンテナンスで長時間の停電があって、その停電からの復帰後に起動したら「iSCSIの論理ボリュームを作成出来ない」という旨のエラーがストレージ側のログに出て、XenServerからの接続が一切出来なくなってしまった。

TeraStationの管理画面を開いて色々やって見たけれどもどうもらちが開かないので、TeraStationからHDDを取り出してPC(サーバー)に直接接続してみた。Raid1で4本のHDDを2本ずつ別々にミラーリングしているだけだったので、1本だけ取り出してもそのまま読めるだろうと思ったのだ。

まずはPC上でDVDからUbuntuを起動する。でもそのままではHDDはマウント出来なかった。かなり焦りながら調べた所、どうやら mdadm コマンドでSoftware Raidとして認識させる必要があるらしいという事が分かった。
ソフトウェアRAIDのデグレード(縮退)モードからの復旧 - RX-7乗りの適当な日々

mdadmを使ったRAIDの再構成 - 日々雑文



mdadmでミラーリングを認識


まずは、
sudo apt-get install mdadm
でmdadmをインストール。
mdadm --examine --scan
cat /proc/mdstat
これでRaidの認識は出来た。次に、
mdadm --run /dev/md0
mdadm --run /dev/md1
mdadm --run /dev/md2
とやると、ミラーリングされたデバイスに片肺状態でアクセス可能になる。
cat /proc/mdstat
でACTIVEと表示されるのが確認出来る。



lvm2で論理ボリュームを認識


ここまで来たら、lvm2で論理ボリュームを確認する所までは一気に行ける。
sudo apt-get install lvm2
pvscan
vgscan
lvscan

これで論理ボリュームの情報が表示されればOK。保存されていた仮想マシンのディスクイメージの分だけ論理ボリュームが存在している。

この論理ボリュームを何とかUbuntuでマウント出来れば、データが取り出せるはずだ。よし、復旧までもう少し。。。

と思ったが、ここからがまた結構長かった。データがそこにあるのが分かっているのに取り出せない、という辛い状態で数時間を費やす事になってしまった。

しょんぼり技術メモ

LVMの論理ボリュームをddでコピー - litediary




ddで吸いだす


とりあえずlvmの論理ボリュームのままでは扱いにくいので、 dd コマンドでボリュームを丸ごと別のディスクにファイルとしてコピーしておく。実はddコマンドなんて普段使う事がほとんど無いので、勉強になった。(笑)
dd if=/dev/VS_XenStorage..(論理ボリュームの名前) of=/media/EXTERNAL/data.vhd bs=4M
これで論理ボリュームからdata.vhdというファイルにコピー出来た。

さて、XenServerの仮想マシンのイメージはVHD形式で保存されているらしいという事は分かったのだが、それがどうやってもマウント出来ない。

最初は losetup と kpartx と言うコマンドでループバックデバイスを構成してから mountコマンドでマウントという方法を試した。下の例の様に、その方法で解決出来たという情報がいくつか見つかったからだ。
Citrix Forums : Unable to mount a VHD in Dom0 ...

CTX117791 - How To Mount a Linux LVM Partition in a XenServer Host - Citrix Knowledge Center

CTX116183 - How To Mount a XenVM Filesystem on XenServer Host - Citrix Knowledge Center

Xenのイメージファイルをマウントする « そこはかと

lvmのディスクイメージをmount - 間違いだらけの備忘録

LVMで使っていたドライブからデータを救出する - kinneko@転職先募集中の日記

上の方法ではどうしても上手く行かなかったので、さらに検索。。。

次に、vdfuse というコマンドでVHDがマウント出来るらしいという事でやって見たが、これもなぜか上手く行かなかった。
Mounting a VHD in Linux




VirtualBoxでVHDをアタッチ


一晩空けて、ふと思い付いた。VirtualBoxの仮想マシンにVHDファイルをアタッチして見たらどうだろうか。。。結果、これが見事に成功だった。VirtualBox上のUbuntu仮想マシンで、アタッチされたディスクをマウントして無事データを復旧する事が出来た。

ただこの方法では、VirtualBoxでアタッチする時にエラーになって出来なかったケースもあった。確か「Parent file with UUID XXXX-XXX-XXXX-....XXX cannot be found...」みたいなエラーメーッセージだった。

これは、どうもXenServerでスナップショット機能を使っていた場合に起こる様だ。スナップショットによって複数の論理ボリュームにディスクイメージのデータが分散してしまっている場合は、VirtualBoxでは上手くアタッチ出来ないのかも知れない。

あと、qemu-img のコンバート機能を使ってもVHDからRAWフォーマットに変換出来るそうだ。RAWフォーマットに変換出来れば、あとは上記のlosetup と kpartxを使う方法でマウント可能になる。
qemu-img convert -f vpc -O raw data.vhd data.raw



XenConvertで仮想マシンを復旧


データをVHDのイメージから取り出す事は上の方法で出来たが、仮想マシン自体をもう一度XenServerで起動するにはどうしたら良いだろうか。データだけ取り出すよりも、元の仮想マシンがそのまま動かせればそれに越した事はない。

まず考えたのは、上でコピーしたdata.vhdファイルの中味を、ddコマンドでXenServerのローカルストレージに作った領域に書き戻してやるという方法だった。理論的にはこれでバッチリ復旧だと思った。でも実際にローカルストレージに元のイメージと同じサイズの仮想ディスクを作成して、そこに dd コマンドで仮想マシンのイメージをそのまま書きこんで起動しようとしても、エラーになって出来なかった。

そこで最後の手段として、VHDのイメージをXenConverterで読み込んで、変換先に「XenServer」を指定して変換して見た。これはなんとか上手く行った。XenConverterでの処理は1台の仮想マシンにつき2時間~5時間ぐらいかかるので大変だったけれども、最終的にはほとんどの仮想マシンをこの方法で元通り稼働させる事が出来た。

復旧後のXenServerのホストはもちろん「TeraStation無し」で動いている。今はローカルストレージをメインで使って、プラス別のLinuxサーバーのNFSシェアを共有ストレージとして使っている。

「NFSは遅いだろう」という先入観があったけれども、TeraStationのiSCSIの時と比べると変わらない。というよりもむしろパフォーマンスは良くなっているかも知れない。









 

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