神経衰弱アプリを作ってみる
10.カードの一致をプログラムで判定させる
前回はカードを表にしたり裏にしたりできるようにして、まーここまででも神経衰弱はできないことはない。
実際に本物のカードを並べて神経衰弱をやる要領で、アプリ上でカードを開いたり閉じたりすればいいんだからね。
でもこのままだと開いた2枚のカードが一致しているかどうか、自分で判定しなきゃいけない。
そんなの面倒くさくって、やってらんねーよ!こちとら江戸っ子だ気が短けーんだ!
などというベランメーな人のために、今回はカードが一致しているかどうかをプログラムで判定できるようにしてみよう。
そんで正解だったときは、みのもんた風に「正解!!」とか表示されるようにすればミリオネアな感じで良くね?
ということで、追加変更したのが以下のプログラム。
例によって赤字が追加変更部分なので、その辺に注目してまずはご覧あれ。
package android.test;
import java.util.ArrayList;
import java.util.Collections;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.Display;
import android.view.Menu;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TableLayout;
import android.widget.TableRow;
public class AndroidTestActivity extends Activity {
static final int ROW = 6; //セルの行数・・・(1)
static final int COL = 4; //セルの列数・・・(2)
//カードの配列
Card[] card = new Card[ROW*COL]; //・・・(11)
@Override
public void onCreate(Bundle savedInstanceState) {
//ステータスバーを消去
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); //・・・(3)
//タイトルバーを消去
requestWindowFeature(Window.FEATURE_NO_TITLE); //・・・(4)
super.onCreate(savedInstanceState);
setCards(); //・・・(12)
TableLayout tableLayout = getTableLayout(); //・・・(5)
setContentView(tableLayout); //・・・(6)
}
/**
* カードの配列にランダムに画像をセット
*/
private void setCards() {
//カードの表の画像(配列)
TypedArray images = getResources().obtainTypedArray(R.array.card_images);
//imageArrayを使って、すべての画像番号をシャッフルする
ArrayList<Integer> imageArray = new ArrayList<Integer>();
for ( int i = 0; i < images.length(); i++ ) {
imageArray.add(i);
}
Collections.shuffle(imageArray);
//cardArrayを使って、カードの数分の画像番号をペアで取り出しシャッフルする
ArrayList<Integer> cardArray = new ArrayList<Integer>();
for ( int i = 0; i < ROW*COL/2; i++ ) {
cardArray.add(imageArray.get(i));
cardArray.add(imageArray.get(i));
}
Collections.shuffle(cardArray);
//カードの配列に、シャッフルした画像番号と画像をセットする
for (int i=0; i < ROW*COL; i++) {
card[i] = new Card(
cardArray.get(i),
images.getDrawable(cardArray.get(i))
);
}
}
/**
* テーブルレイアウトを返却
* @return TableLayout
*/
private TableLayout getTableLayout() {
int cellNum = ROW * COL; //全体のセルの数
TableLayout tableLayout = new TableLayout(this);
TableRow tableRow = null;
for (int i = 0; i < cellNum; i++) {
if (i % COL == 0) {
tableRow = new TableRow(this);
tableLayout.addView(tableRow);
}
Button button = new Button(this);
button.setId(i); //・・・(7)
button.setHeight(getCellHeight()); //・・・(8)
button.setWidth(getCellWidth()); //・・・(9)
//ボタンに裏面の画像をセット
button.setBackgroundDrawable(
getResources().getDrawable(R.drawable.card_back)
);
setEvent(button); //・・・(10)
tableRow.addView(button);
}
return tableLayout;
}
/**
* 行数と画面の幅からセルの高さを算出
* @return セルの高さ
*/
private int getCellHeight() {
WindowManager windowManager = getWindowManager();
Display display = windowManager.getDefaultDisplay();
return display.getHeight() / ROW;
}
/**
* 列数と画面の幅からセルの幅を算出
* @return セルの幅
*/
private int getCellWidth() {
WindowManager windowManager = getWindowManager();
Display display = windowManager.getDefaultDisplay();
return display.getWidth() / COL;
}
/**
* 各ボタンビューにイベントをセット
* @param button
*/
private void setEvent(Button button) {
//タップ時の動作
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Button button = (Button)v;
int cardId = button.getId();
if(card[cardId].isCardOpen()) { //表になっているカードをタップしたとき
if (!card[cardId].isCardMatch()) {
card[cardId].reverseCard(); //・・・(13)
dispCard(button, card[cardId]); //・・・(14)
}
} else { //裏になっているカードをタップしたとき
card[cardId].reverseCard(); //・・・(13')
dispCard(button, card[cardId]); //・・・(14')
for (int i = 0; i < ROW*COL; i++) {
if(card[i].isCardOpen()) {
if (i != cardId && card[i].getImgNo() == card[cardId].getImgNo()) {
//カードが一致していたとき
matchedDialog();
card[i].setCardMatch();
card[cardId].setCardMatch();
}
}
}
}
}
});
}
/**
* カードの画像を表示する
*/
private void dispCard(Button button, Card card) {
if(card.isCardOpen()) {
//表面
button.setBackgroundDrawable(card.getCardImg());
} else {
//裏面
button.setBackgroundDrawable(
getResources().getDrawable(R.drawable.card_back)
);
}
}
/**
* カードが一致したときのメッセージ・ダイアログを表示する
*/
private void matchedDialog() {
new AlertDialog.Builder(AndroidTestActivity.this).setPositiveButton(
"OK",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
}
).setMessage("正解!!\n(みのもんた風に)").show();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_android_test, menu);
return true;
}
}
/**
* カードのクラス
*/
class Card {
private int imgNo; //画像番号
private Drawable cardImg; //画像
private boolean cardOpen; //カードが表になっているとき:true
private boolean cardMatch; //カードが他のカードと一致しているとき:true
Card(int imgNo, Drawable cardImg) {
this.imgNo = imgNo;
this.cardImg = cardImg;
this.cardOpen = false;
this.cardMatch = false;
}
int getImgNo() {
return imgNo;
}
Drawable getCardImg() {
return cardImg;
}
boolean isCardOpen() {
return cardOpen;
}
void reverseCard() {
cardOpen = !cardOpen;
}
void setCardMatch() {
cardMatch = true;
}
boolean isCardMatch() {
return cardMatch;
}
}
では最初に、前回同様Cardクラスにご注目。
今回はココに
private boolean cardMatch;
として、フィールドを1つ追加した。
これは、そのカードが既に他のカードと一致済みかどうかを保持するフィールドだ。
前回追加した「cardOpen」と同様に「boolean」のフィールドで、既に一致している場合に「true」となるようにした。
そしてその下のコンストラクタでは、このフィールドの初期値として「false」をセットしてある。
初期状態ではカードは裏返しになっていて、他のカードと一致してるはずはないのだから当然だね。
さらにそのずっと下のほうではセッターとして、「setCardMatch」のメソッドが定義してある。
これは言うまでもなく、そのカードが他のカードと一致したときに「cardMatch」フィールドに「true」をセットするためのメソッドだ。
加えて「isCardMatch」という、「cardMatch」フィールドの値を取得するためのメソッドも定義した。
つまり、ゲッターメソッドだね。
さて、Cardクラスに必要なフィールドとメソッドを追加したら、今度は上のほうに戻って「setEvent」のメソッドの中の変更部分を見てもらいたい。
前回では、ここでカードの裏表を変更するように処理を加えたね。
/**
* 各ボタンビューにイベントをセット
* @param button
*/
private void setEvent(Button button) {
//タップ時の動作
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Button button = (Button)v;
int cardId = button.getId();
if(card[cardId].isCardOpen()) { //表になっているカードをタップしたとき
if (!card[cardId].isCardMatch()) {
card[cardId].reverseCard(); //・・・(13)
dispCard(button, card[cardId]); //・・・(14)
}
} else { //裏になっているカードをタップしたとき
card[cardId].reverseCard(); //・・・(13')
dispCard(button, card[cardId]); //・・・(14')
for (int i = 0; i < ROW*COL; i++) {
if(card[i].isCardOpen()) {
if (i != cardId && card[i].getImgNo() == card[cardId].getImgNo()) {
//カードが一致していたとき
matchedDialog();
card[i].setCardMatch();
card[cardId].setCardMatch();
}
}
}
}
}
});
}
今回はカードが一致しているかどうかを判定できるように、さらに変更を加えてみたよ。
まずは見て分かるようにif文を使って、タップしたカードの状態が裏か表かで処理を分けるようにした。
前回の処理と同様に表になってるカードをタップした場合は裏に戻すんだけれども、今回は既に他のカードと一致している場合は裏に戻さないようにした。
ここで先ほど追加した「cardMatch」フィールドの値を「isCardMatch」メソッドを使って参照して、タップしたカードが他のカードと一致済みかどうか判定してるね。
そして(13)(14)のところは前回のままで、タップしたカードがまだ他と一致していない場合は裏返しに戻す処理が実行される。
ここまでが、表になっているカードをタップした場合の処理だ。
続いてその下が、裏になっているカードをタップした場合の処理。
(13')(14')となっている行は上の(13)(14)と同じで、前回の処理のままだ。
重要なのはその下の、for文のところ。
これは、表にしたカードが他の表になっているカードと一致しているかどうかの判定をするための処理だ。
ちょっとネストが深くなって分かり難いかもしれないけれども、やってる処理はforループを使って全カードについて調べているだけだね。
ただ全カードと言っても、if文を使って対象となるカードを「表になっているカード」に限定していて
if(card[i].isCardOpen())
として、「isCardOpen」メソッドを使って判定をするようにした。
そしてそのカードと、現在タップして表にしたカードとをif文によって比較しているのがここ。
if (i != cardId && card[i].getImgNo() == card[cardId].getImgNo())
これはそれぞれのカードについて「getImgNo」で画像番号を取得して、その2つの番号を比較することによってカードが一致しているかどうかを判定している。
もちろんforループで調べている全カードの中には現在タップしたカードも含まれているので、それについてはif文の前半
i != cardId
によって除外している。
そして、比較する2枚のカードが一致していた場合のみ
matchedDialog()
によって、メッセージのダイアログを表示して、次に
card[i].setCardMatch()
によって、該当するカードの「cardMatch」フィールドに「true」をセットし、最後に
card[cardId].setCardMatch()
によって、タップして表にしたカードの「cardMatch」フィールドにも「true」をセットしている。
メッセージのダイアログを表示する「matchedDialog」については、その少し下のほうに定義してあるのは言わずもがな。
これについては、前に何度かダイアログについて触れたので特に問題無いはずだ。
以上で、2枚のカードが一致しているかどうかをプログラムで判定できるようになったね。
めでたし、めでたし・・・と思ったら大間違い。
というのも、このままだとズルいやり方を許してしまうからだ。
つまり表にしたカードをそのままにして、次々にカードを開いていくというやり方ができるんだよね。
実際やってみると分かるけれども、こんな感じ。
カードを次々開いていくと確かに一致したカードが現れたときにダイアログが表示されるけれども、こんなのどんどんやっていけば一致するのが当然だよね。
そんなやり方は神経衰弱じゃない!カードを表にできるのは2枚までだ、3枚以上表にするなんて不正は許さないぞ。
正直者がバカをみる世の中を正すために、このプログラムも正される必要があるのだ。
というわけで表にできるカードが2枚までになるように、さらにプログラムに変更を加えるんだけれども。
それは、次項にて。