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

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

このパズルは、ドロイドくんの頭とお尻が画かれたパネルを入れ替えて、全部を揃えるゲームです。頭とお尻には右向き、左向きがあって、色も4色あるので、なかなかうまく合いません。クリックした2枚のパネルの場所が入れ替わります。クリックするパネルは隣り合ったものでなくてもかまいません。操作は単純ですが、意外に難しいパズルです。
DroidPuzzleの実行例
![]() |
→ | ![]() |
以下のような設定で新しいアプリを作成してください。
項目名 | 設定する内容 |
---|---|
Application Name | DroidPuzzle |
Company Domain | sample.example.com |
このアプリでは、以下のようにウィジェットを配置します。表には配置するImageViewウィジェットとして、panel1しか掲載していませんが、同様にpanel9まで配置します。パネルの位置は実行例の画面を参考にしてください。
ウィジェット | プロパティ | 設定値 | 備考 |
---|---|---|---|
ImageView | id | panel1 | ウィジェットのID |
background | #C0C0C0 | 見出しの背景色 | |
Button | text | シャッフル | |
onclick | shuffleProc | クリックされたときに実行されるメソッド | |
Button | text | 答を見る | |
onclick | showAnswer | クリックされたときに実行されるメソッド | |
Button | text | 終了 | |
onclick | exitProc | クリックされたときに実行されるメソッド |
パネルはすべて用意できているので、プログラムのほうでやることは、クリックされたら絵を入れ替える、絵がすべて揃ったかを調べるという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: }
- (パネルの)クリックはアクティビティでまとめて処理
- この配列が重要。パネルと画像を対応させるための配列
- パネルを交換するためのメソッド
- 現在の画像の番号をセーブ
- 直前にクリックされた画像の番号を現在のパネルに入れる
- 現在の画像の番号を直前にクリックされたパネルに入れる
- パネルの0~8に、画像が同じ順序で(0~8まで)入っているかどうかを調べる。パネルと画像の順序が同じなら完成。
- ちょっとトリッキー。panel1~9のリソースIDを、それぞれのパネルの番号(0~8)に変換して渡している
- パネルと画像をランダムに対応させる
パネルとイメージの対応表を作る
パネルは左上から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には連続した値が付けられるとは限らないので、このテクニックを使うときは注意が必要です。
あとは、少しずつコードを読み解いていってみてください。
今回解説したのは書籍のサポートページにある「さらに高度なアプリ」の中の「DroidPuzzle」というサンプルです。本書で扱っている他のアプリもぜひ遊んでみてください。
http://book.impress.co.jp/books/1114101120_4
このサンプルでは、3×3のパネルを使いましたが、4×4にするともっと難しくなります。好きな画像を使って、自動的にこのゲームを作るようなしくみを用意したり、点数やランキングが付けられるようにすると、本格的な(Google Playストアでも販売できるレベルの)アプリケーションにできそうですね。
この記事のもとになった書籍はこちら! | |
---|---|
![]() 羽山 博/めじろまち 著 |
イラストでよくわかるAndroidアプリのつくり方—Android Studio対応版プログラミング未経験でも大丈夫! Android Studio対応のAndroidアプリ開発入門、決定版。好評だった前作『イラストでよくわかるAndroidアプリのつくり方』に改訂版が登場。親しみやすいイラストやステップバイステップでの丁寧な解説といった基本コンセプトを踏襲しつつ、最新版のSDKや、Androidの新しい開発環境である「Android Studio」に対応させました! Androidのプログラムを作りながら、自然にJavaというプログラム言語の知識が身につくようになっています。 |