AnaQRam を作りました

IGGG 名古屋支部 支部長の ひげ です。
今回は群大理工学部の学園祭 群桐祭 のイベント、テクノドリームツアー用に作成した、AnaQRam という Android アプリについて紹介したいと思います。

AnaQRam とは

宝探しとパズルをコンセプトにした、すごく簡易的なゲームです。
テクノドリームツアーは小学校低学年前後を対象にした科学体験イベントであり、それ用に作成したので、ゲーマーにはかなり退屈なゲームです。

遊び方は簡単で、まずQRコードをスキャンしまくって、パズルのピースとなる文字を集めます。
文字を見つけると が対応する文字に変わります。

文字を集めている

文字を集めきったら次はパズルです。
集めた文字を並び替えて正しい文字列を作ります(アナグラム)。

文字を並び替えている

ゲームとしてはこんな感じです。

このQRコードを色んな所に隠せば、結構面白いかなぁと考えて作りました。

(本番ではスペース的に隠す余裕無くて、全部壁一面に貼られてしまったが…)

実装

他の IGGG メンバもコードを読めるように(誰も読んでないだろうけど)、ベーシックに Java と Android Studio を使って作成しました。

コードは全て、私のリポジトリ に公開してあります。

全体的に、WEB上を検索して得た機能をどんどんマッシュアップしていった感じです。

(そもそも、自分で一から Android アプリを作ったのは始めて)

端末の環境

端末は群大情報科で借りた、Nexus7 を使うのですが、Android のバージョンが 5.1.1 (Lollipop) だったので、それに合わせて開発しました。

(そのため Java8 が使えない…)

また、Google Play 開発サービスの Mobile Vision API のQRコードを読み取るライブラリを使ったため、開発サービスのバージョンを 7.8 以上にする必要があります。

(このため、前日に端末の開発サービスのバージョンを全て挙げる作業が…)

他にも日本語入力するのに Google 日本語キーボードや端末の言語を日本語にする必要があります( ゲーム自体は日本語でなくても動作するので、英語のみでアナグラムをするのであれば必要ない 伏字の が全角なのでダメだ、あとで直します…)。

開発環境

  • Windows 10 Home
  • Android SDK Build-tools 24.0.2
    • min SDK 21
  • Android Studio 2.2

QRコードを読み取る

以下のWEBサイトを参考にしました。

上述したとおり、Google の Mobile Vision API を利用しています。

QRコードを読み取る他の方法として、サードパーティ製の ZXing というライブラリもありましたが、なんとなく純正のやつを使ってみました。

記事の通りに書いていったらうまく動きました。
特に難しいことしておらず、SurfaceView にカメラを貼り付けて、読み取った画像を API で解析し、見つけた時の動作を後から与えているだけです。
API の仕組み等までは流石に知らないので説明しません。

精度はかなり良く、画面に複数のQRコードが写っていても、すぐに全部読み取ってくれます。
QRコードは小さくても良く、ピントさへ合えば問題なさげです。

ただ、API のせいか、実機デバッグに使っていた私の端末のせいかわからないのですが、使ってると時々カメラが真っ暗になって映らなくなってしまいます…(再起動すればまた映りますが…)

中の仕組み

超概略図

文字は CharBox という char 型のラッパークラスを使って管理しています。
既に見つけたかどうかの flagboolean 型で持っていて、これの真偽で toString した時の返す文字列が変わります(もちろん false のときが ?)。

class CharBox {
private char value;
private boolean flag;
private final static char defaultValue = '?';
CharBox(char c) { ... }
void setFlag() { ... }
void resetFlag() { ... }
@Override
public String toString() {
return String.valueOf(flag ? value : defaultValue);
}
}

AnaQRam の内部では答えの文字列を決めると、それを CharBox の配列へと変換します。

QRコードは実は、文字では無くただの 数字を表していて、スキャンすると対応する CharBox 配列のインデックスにアクセス して、フラグを立てます。
こうすることで、アプリ内で答えの文字列を変更しても、貼るQRコードを変更しなくても良い ようにしてます。

もちろん、インデックス以上の数字を読み取っても例外を投げて落ちたりしません。
数字以外の場合は、「QRコードが対応してないよ!」的なメッセージを出します。
数字の場合は、インデックスに使う前に、答えの文字列長で Modulo (余りを求める演算) を取ります。
そうすることで、文字列長以上の数字が来ても、問題なく扱うことができます(文字列長に合わせて貼りなおす必要がない、もちろん最大に合わせる必要はあるが)。

String displayChar(String qrText) {
try {
// 剰余を取って文字数未満の数字が出ても大丈夫にしている
int index = Integer.valueOf(qrText) % charBoxes.length;
charBoxes[index].setFlag();
return "「" + charBoxes[index] + "」をみつけた!v(≧∇≦)v";
} catch (NumberFormatException e) {
// qrText が数字以外の場合
return "ちがうQRコードだよ!(* ̄∀ ̄)\"b\" チッチッチッ";
}
}

画面への表示はタダのボタンになっています。
このボタン配列に CharBox 配列をマッピングします。
ボタンを押して入れ替えるときは、このマッピングを変えています。

正解かどうかを確かめるときは、ボタン配列から文字列を生成(つまり、伏字が混ぜっていれば り?ご となる)して、答えの文字列との equals を取ります。

その他の機能

他にも、トーストを用いたクリアメッセージ表示や、タイマー機能の追加などをしています。
参考文献は全てREADMEに書いてあるので、見てください。

困った点

そもそも Android をあまりやってなかったので、初めの方はデバッグで苦労しました。
Android Studio にはブレークポイントや逐次実行もあるので慣れてしまえばどうとでもなりますが、やっぱり普段関数型を使ってる身としては、例外ばっかのデバッグはつらいですね…

あとは、画面の回転時にビューを再構築してしまう仕様に苦労しました。
再構築してしまうため、履歴がリセット、見つけた文字が伏字に戻ってしまう。
結局、一番簡単な方法で落ち着いたのですが、その代わりにカメラが回転してくれないので、実質回転不可です。
今後直していきたいですね。

今後

個人的には気にっているので、機能を追加・修正していこうと思ってます。
ただ、単体では面白くない(QRコードがないとダメ、つまりイベントとかでしか使えない)なので、Play Store にはあげないと思います。

おわりに

端末にインストールするのにそこそこ苦労したんですが、他に用意した Unity のゲームは難なくインストールできててスゲーって思いました。
今度は Unity もいじってみたいですねぇ。