Android Studioサンプルアプリ解説集 - パズルゲームDroidPuzzle

2015年7月28日(火)
羽山 博(はやま ひろし)
イラストでよくわかるAndroidアプリのつくり方—Android Studio対応版
Amazon詳細ページへ
この記事では、書籍『イラストでよくわかるAndroidアプリのつくり方—Android Studio対応版』の解説内容をもとに、本書で説明しきれなかったサンプルアプリを解説しています。

このパズルは、ドロイドくんの頭とお尻が画かれたパネルを入れ替えて、全部を揃えるゲームです。頭とお尻には右向き、左向きがあって、色も4色あるので、なかなかうまく合いません。クリックした2枚のパネルの場所が入れ替わります。クリックするパネルは隣り合ったものでなくてもかまいません。操作は単純ですが、意外に難しいパズルです。

DroidPuzzleの実行例

以下のような設定で新しいアプリを作成してください。

項目名設定する内容
Application NameDroidPuzzle
Company Domainsample.example.com

このアプリでは、以下のようにウィジェットを配置します。表には配置するImageViewウィジェットとして、panel1しか掲載していませんが、同様にpanel9まで配置します。パネルの位置は実行例の画面を参考にしてください。

ウィジェットプロパティ設定値備考
ImageViewidpanel1ウィジェットのID
background#C0C0C0見出しの背景色
Buttontextシャッフル
onclickshuffleProcクリックされたときに実行されるメソッド
Buttontext答を見る
onclickshowAnswerクリックされたときに実行されるメソッド
Buttontext終了
onclickexitProcクリックされたときに実行されるメソッド

パネルはすべて用意できているので、プログラムのほうでやることは、クリックされたら絵を入れ替える、絵がすべて揃ったかを調べるという2つの処理だけです。といっても、かなり複雑になりますが。

コードがかなり長くなるので、ポイントのみ示しておきました。「……」の後の太字のキャプションをざっと眺め、どのようなことをやっているのかが大まかにつかんでください。

Java > src/com.example.sample.droidpuzzle/MainActivity.java

  1: package com.example.sample.droidpuzzle;
  2:
  3: import android.support.v7.app.ActionBarActivity;
  4: import android.os.Bundle;
  5: import android.view.Menu;
  6: import android.view.MenuItem;
  7: import android.view.View;
  8: import android.widget.ImageView;
  9: import android.widget.Toast;
 10:
 11: import java.util.Random;
 12:
 13:
 14: public class MainActivity extends ActionBarActivity implements View.OnClickListener {…… (1)
 15:     final int panelId[] = {R.id.panel1, R.id.panel2, R.id.panel3,
 16:             R.id.panel4, R.id.panel5, R.id.panel6,
 17:             R.id.panel7, R.id.panel8, R.id.panel9
 18:     };        // パネル1~9のID
 19:     final int imageDrawableId[] = {R.drawable.image1, R.drawable.image2, R.drawable.image3,
 20:             R.drawable.image4, R.drawable.image5, R.drawable.image6,
 21:             R.drawable.image7, R.drawable.image8, R.drawable.image9
 22:     };        // 画像1~9のDrawable
 23:     int panelToDrawable[] = new int[9];    // パネルに表示されている画像(Drawable)…… (2)

 24:     ImageView panelImageView[] = new ImageView[9];    // パネルを参照する変数
 25:
 26:     int clickedIndex;        // クリックされたパネルのインデックス(0~8)
 27:     boolean isClicked = false;    // パネルがクリックされているか
 28:
 29:     @Override
 30:     protected void onCreate(Bundle savedInstanceState) {
 31:         super.onCreate(savedInstanceState);
 32:         setContentView(R.layout.activity_main);
 33:         // ウィジェットの取得
 34:         for (int i = 0; i < 9; i++) {
 35:             panelImageView[i] = (ImageView) this.findViewById(panelId[i]);
 36:         }
 37:         shuffle();    // シャッフルする
 38:     }
 39:
 40:     private void swapPanel(int i) { …… (3)
 41:         if (isClicked) {    // 直前にパネルがクリックされている場合
 42:             // panelToDrawableに記憶されているDrawableのインデックスを交換する
 43:             int temp = panelToDrawable[clickedIndex]; …… (4)
 44:             panelToDrawable[clickedIndex] = panelToDrawable[i]; …… (5)
 45:             panelToDrawable[i] = temp; …… (6)
 46:             // パネルを表示しなおす
 47:             panelImageView[i].setImageResource(imageDrawableId[panelToDrawable[i]]);
 48:             panelImageView[clickedIndex].setImageResource(imageDrawableId[panelToDrawable[clickedIndex]]);
 49:             panelImageView[i].setAlpha(1.0F);
 50:             panelImageView[clickedIndex].setAlpha(1.0F);
 51:             // 完成の判定
 52:             for (int idx = 0; idx < 9; idx++) {…… (7)
 53:                 if (idx != panelToDrawable[idx]) {
 54:                     isClicked = !isClicked;
 55:                     return;    // 違っていた時点で抜ける
 56:                 }
 57:             }
 58:             // ここにたどりつくということは完成したということ
 59:             Toast.makeText(this, "揃いました", Toast.LENGTH_SHORT).show();
 60:         } else {            // 直前にパネルがクリックされていない場合
 61:             clickedIndex = i;    // このパネルのインデックスをセーブしておく
 62:             panelImageView[i].setAlpha(0.8F);    // 選択されたことが分かるように少し薄く表示する
 63:         }
 64:         isClicked = !isClicked;
 65:     }
 66:
 67:     @Override
 68:     public void onClick(View v) { …… (8)
 69:         swapPanel(v.getId() - R.id.panel1);    // panel1~panel8のIDが連続した値であることを利用
 70:
 71:     }
 72:
 73:     public void showAnswer(View v) {    // 答えを表示する
 74:         for (int i = 0; i < 9; i++) {
 75:             panelToDrawable[i] = i;
 76:             panelImageView[i].setImageResource(imageDrawableId[i]);
 77:         }
 78:     }
 79:
 80:     public void shuffleProc(View v) {
 81:         shuffle();
 82:     }
 83:
 84:     public void exitProc(View v) {
 85:         this.finish();
 86:     }
 87:
 88:     private void shuffle() { …… (9)
 89:         for (int i = 0; i < 9; i++) {
 90:             panelToDrawable[i] = 0;    // パネルとdrawableの対応表を初期化(空にする)
 91:         }
 92:         // パネルをランダムに表示する
 93:         Random r = new Random();
 94:         for (int i = 0; i < 9; i++) {
 95:             int temp;
 96:             do {
 97:                 temp = r.nextInt(9);        // 0~8の乱数
 98:             } while (panelToDrawable[temp] != 0);    // 空きでない間、乱数を作り続ける
 99:             panelToDrawable[temp] = i;    // 空きパネルに入れる
 98:         }
 99:         for (int i = 0; i < 9; i++) {    // パネルを表示する
100:             panelImageView[i].setImageResource(imageDrawableId[panelToDrawable[i]]);
101:             panelImageView[i].setOnClickListener(this);
102:         }
103:     }
104:
:
127: }
  1. (パネルの)クリックはアクティビティでまとめて処理
  2. この配列が重要。パネルと画像を対応させるための配列
  3. パネルを交換するためのメソッド
  4. 現在の画像の番号をセーブ
  5. 直前にクリックされた画像の番号を現在のパネルに入れる
  6. 現在の画像の番号を直前にクリックされたパネルに入れる
  7. パネルの0~8に、画像が同じ順序で(0~8まで)入っているかどうかを調べる。パネルと画像の順序が同じなら完成。
  8. ちょっとトリッキー。panel1~9のリソースIDを、それぞれのパネルの番号(0~8)に変換して渡している
  9. パネルと画像をランダムに対応させる

パネルとイメージの対応表を作る

パネルは左上から0、1、2……8という番号を付けておきます。正解のイメージも0、1、2……8という番号になっています。ゲームを始めるか[シャッフル]ボタンをクリックすると、88行目~103行目のコードが実行されます。これは、パネルと画像の対応をランダムにして、次の図の左側のようにするコードです。

私たちが、パネルをクリックすると、配列の中のデータが入れ替えられます。そのためのコードが40行目~65行目の(3)のswapPanelメソッドです。このメソッドの中では、44行目と45行目で画面に表示されている画像も入れ替えます。また、52行目の⑦以降で、パズルが完成したかどうかを調べます。

なお、データや画像の入れ替え、パズルが完成したかどうかのチェックは、2回目のクリックのときに実行されます。パネルを2枚クリックして入れ替えるわけですから。最初のクリックか、2回目のクリックかを記憶しておくために、boolean型のisClickedという変数を使っていることにも注目です。

最終的には、図の右側のようになればすべてが揃った状態です。つまり、idxの値とpanelToDrawable[idx]の値がすべて等しくなれば、パズルの完成です。

シャッフルされた状態 → こういう状態になっていれば、すべてが揃っている

シャッフルされた状態 → こういう状態になっていれば、すべてが揃っている

画面の表示

画面の表示

要するに、クリックして選択された要素を入れ替え、右のような状態にするのがこのゲーム。画面には、数字ではなく、ドロイドくんが表示されているというわけ。

リソースIDをもとに画像の番号を得るトリック

68行目の(8)では、クリックされたImageViewのIDを画像の番号に変換するために、

arg0.getId()-R.id.panel1

というコードを書いています。これは、次の図のようにpanelのIDが(たまたまですが)順に並んでいるのを利用したトリックです。

クリックされたパネルがpanel1なら、arg0.getId()は0x7f09003fとなります。R.id.panel1の値も0x7f09003fなので、引き算をすれば0になります。クリックされたパネルがpanel2なら、arg0.getId()は0x7f090040となります。R.id.panel1の値は0x7f09003fなので、引き算をすれば1になります。このようにして、クリックされたパネルのIDを配列のインデックスに変換しているわけです。

※リソースIDには連続した値が付けられるとは限らないので、このテクニックを使うときは注意が必要です。

※リソースIDには連続した値が付けられるとは限らないので、このテクニックを使うときは注意が必要です。

あとは、少しずつコードを読み解いていってみてください。

今回解説したのは書籍のサポートページにある「さらに高度なアプリ」の中の「DroidPuzzle」というサンプルです。本書で扱っている他のアプリもぜひ遊んでみてください。
http://book.impress.co.jp/books/1114101120_4

このサンプルでは、3×3のパネルを使いましたが、4×4にするともっと難しくなります。好きな画像を使って、自動的にこのゲームを作るようなしくみを用意したり、点数やランキングが付けられるようにすると、本格的な(Google Playストアでも販売できるレベルの)アプリケーションにできそうですね。

この記事のもとになった書籍はこちら!
イラストでよくわかるAndroidアプリのつくり方—Android Studio対応版

羽山 博/めじろまち 著
価格:2,200円+税
発売日:2015年6月5日発売
ISBN:978-4-8443-3813-0
発行:インプレス

イラストでよくわかるAndroidアプリのつくり方—Android Studio対応版

プログラミング未経験でも大丈夫! Android Studio対応のAndroidアプリ開発入門、決定版。好評だった前作『イラストでよくわかるAndroidアプリのつくり方』に改訂版が登場。親しみやすいイラストやステップバイステップでの丁寧な解説といった基本コンセプトを踏襲しつつ、最新版のSDKや、Androidの新しい開発環境である「Android Studio」に対応させました! Androidのプログラムを作りながら、自然にJavaというプログラム言語の知識が身につくようになっています。

Amazon詳細ページへImpress詳細ページへ

著者
羽山 博(はやま ひろし)

京都大学文学部哲学科(心理学専攻)卒業後、NECでユーザー教育や社内SE教育を担当したのち、ライターとして独立。ソフトウェアの基本からプログラミング、認知科学、統計学まで幅広く執筆。読者の側に立った分かりやすい表現を心がけている。2006年に東京大学大学院学際情報学府博士課程を単位取得後退学。現在、有限会社ローグ・インターナショナル代表取締役、日本大学、青山学院大学、お茶の水女子大学講師。 最近の趣味は書道、絵画、ウクレレ、ジャグリング、献血。

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています