【はじめてのKotlinプログラミング(14)】配列とシャッフルを使った簡単な四択クイズアプリ(後編:関数をつくってまとめる)

前回、簡単な四択クイズアプリを作りました。
数字の順番に押していくと、ゲームクリア。
途中で間違えると、ゲームオーバー、というものでした。

動作自体はこれで問題ないのですが
不正解のところは、この5行、全部同じです。

tvQuestion.text=”不正解!!Game Over”
btn0.isEnabled = false
btn1.isEnabled = false
btn2.isEnabled = false
btn3.isEnabled = false

btn0も、btn1も、
2も3も全く同じなんですよね。

なので、1か所にまとめる、ということをやっていきましょう。

動画

コード

▼activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <TextView
        android:id="@+id/tvCount"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:layout_marginEnd="24dp"
        android:layout_marginRight="24dp"
        android:text="0問正解"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tvQuestion"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:text="番号順にタッチせよ"
        android:textSize="24sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tvCount" />

    <Button
        android:id="@+id/btn0"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="90dp"
        android:text="Button"
        app:layout_constraintTop_toBottomOf="@+id/tvQuestion"
        tools:layout_editor_absoluteX="187dp" />

    <Button
        android:id="@+id/btn1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:text="Button"
        app:layout_constraintTop_toBottomOf="@+id/btn0"
        tools:layout_editor_absoluteX="129dp" />

    <Button
        android:id="@+id/btn2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:text="Button"
        app:layout_constraintTop_toBottomOf="@+id/btn1"
        tools:layout_editor_absoluteX="160dp" />

    <Button
        android:id="@+id/btn3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:text="Button"
        app:layout_constraintTop_toBottomOf="@+id/btn2"
        tools:layout_editor_absoluteX="140dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

▼MainActivity.kt

package com.example.simplequiz

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AlertDialog

class MainActivity : AppCompatActivity() {
    //2)'配列と変数を用意(クラスの各メソッドで参照可)
    private val quizData = arrayOf("A0","A1","A2","A3")
    private var i =0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //1)viewを取得(idで) + カウント用のiを用意
        val tvCount:TextView =findViewById(R.id.tvCount)
        val tvQuestion:TextView =findViewById(R.id.tvQuestion)
        val btn0:Button = findViewById(R.id.btn0)
        val btn1:Button = findViewById(R.id.btn1)
        val btn2:Button = findViewById(R.id.btn2)
        val btn3:Button = findViewById(R.id.btn3)
        //var i =0

        //2)配列を用意
        //val quizData = arrayOf("A0","A1","A2","A3")

        //4)0~3までのリスト用意⇒シャッフル
        val list = listOf(0,1,2,3)
        val num =list.shuffled()

        //3)ボタンにquizDataの0~3を表示させてみる
        //5)シャッフルされたnumの数字を入れる
        btn0.text =quizData[num[0]]
        btn1.text =quizData[num[1]]
        btn2.text =quizData[num[2]]
        btn3.text =quizData[num[3]]

        //6)btn0を押した時の正誤判定
        btn0.setOnClickListener {
            if(btn0.text==quizData[i]){
                //正解
                correctAns()

                //7)カウントを1増やして無効化
                i++
                btn0.isEnabled =false
                tvCount.text =i.toString() + "問正解"
                //9)i==4でGameClear
                if(i==4){
                    tvQuestion.text="全問正解!!Game Clear!!"
                }

            }else{
                //不正解+ボタンの無効化
                incorrectAns()
            }
        }

        //8)btn1~3も同じようにする
        btn1.setOnClickListener {
            if(btn1.text==quizData[i]){
                //正解
                correctAns()

                //7)カウントを1増やして無効化
                i++
                btn1.isEnabled =false
                tvCount.text =i.toString() + "問正解"

                if(i==4){
                    tvQuestion.text="全問正解!!Game Clear!!"
                }

            }else{
                //不正解+ボタンの無効化
                incorrectAns()
            }
        }

        btn2.setOnClickListener {
            if(btn2.text==quizData[i]){
                //正解
                correctAns()

                //7)カウントを1増やして無効化
                i++
                btn2.isEnabled =false
                tvCount.text =i.toString() + "問正解"

                if(i==4){
                    tvQuestion.text="全問正解!!Game Clear!!"
                }

            }else{
                //不正解+ボタンの無効化
                incorrectAns()
            }
        }

        btn3.setOnClickListener {
            if(btn3.text==quizData[i]){
                //正解
                correctAns()

                //7)カウントを1増やして無効化
                i++
                btn3.isEnabled =false
                tvCount.text =i.toString() + "問正解"

                if(i==4){
                    tvQuestion.text="全問正解!!Game Clear!!"
                }

            }else{
                //不正解+ボタンの無効化
                incorrectAns()
            }
        }

    }
    //11)正解の関数(アラートダイアログを表示)
    private fun correctAns(){
        AlertDialog.Builder(this)
            .setTitle("正解!")
            .setMessage(quizData[i])
            .setPositiveButton("OK",null)
            .show()
    }

    //10)不正解処理の関数
    private fun incorrectAns(){
        val tvQuestion:TextView =findViewById(R.id.tvQuestion)
        val btn0:Button = findViewById(R.id.btn0)
        val btn1:Button = findViewById(R.id.btn1)
        val btn2:Button = findViewById(R.id.btn2)
        val btn3:Button = findViewById(R.id.btn3)

        //不正解+ボタンの無効化
        tvQuestion.text="不正解!!Game Over"
        btn0.isEnabled = false
        btn1.isEnabled = false
        btn2.isEnabled = false
        btn3.isEnabled = false
    }
}

テキスト

前回作ったコードは、不正解のところが5行、全部同じでした。

同じコードは、関数を1つ作ってそこにまとめる、ということをやっていきましょう。

てことで、関数を1つ用意して、
そこにこれらのコードを一か所にまとめていくわけですが、
その前に知識として覚えていただきたいことが1つ2つあります。

1.宣言する場所

まず1つ目は、宣言する場所です。

今までfun onCreate{
}の中で宣言していました。
quizDataとかですね、
上の方にもいろいろありますが。

funっていうのはfunctionの略で、関数あるいはメソッドを意味します。
fun{}の中で宣言したものは
その関数・メソッドの{}の中でしか使えません。

で新しく関数を作って、横断的にこういう配列の要素を使用したい場合は、
fun onCreateの中ではなくて、
その上。
厳密にいうと、クラス名{
の下。
ここに書くと、横断的に使用できるようになります。というのが1つめ。

2.ただし、findViewByIdは除く

もう1つは、今の説明を踏まえた上で、ただし
findViewByIdは、こっちでは使えません。ここがちょっとややこしいんです。
使えないというより、
厳密にはfindViewByIdは
setContentViewより下に書かないと機能してくれません。
上に書いても、エミュレータを起動してしたときクラッシュしてしまいます。

以上2点。
関数を増やしていく上で、必要となる知識。

1つ目は、横断的に使いたい場合はクラス名の下に書く。
ただし、findViewByIdは除く。

こういうのをふんわり、頭の中に入れておいてください。

それでは始めていきましょう。

1.関数を作ろう

それでは始めていきましょう。

この不正解処理だけをまとめた関数を1つ作ります。
作る場所は、
fun onCreate {
}
の外、厳密には「下」ですね。

ここに
//10)不正解処理の関数
を用意します。

書き方は

fun 関数の名前(){
}

と書いていきます。

funっていうのはですね
functionの略で、文字通り関数を意味します。

それでは書いていきましょう。
不正解なので、名前をincorrectAnsとかにしてあげて、
まるカッコ、波カッコ。

fun incorrectAns(){
}

これで1つ関数ができました。
で、この中に、不正解の処理をそっくりそのままコピーして
貼り付ければOKです。

    fun incorrectAns(){
        //不正解+ボタンの無効化
        tvQuestion.text="不正解!!Game Over"
        btn0.isEnabled = false
        btn1.isEnabled = false
        btn2.isEnabled = false
        btn3.isEnabled = false
    }

で、あとは
incorrectAns()
を上の不正解のところにペタペタ貼っていけばOKなんですが。
実は今の状態だと1つだけ問題があります。

赤くなってるのでおわかりの通り、Viewが取得できていません。
ここで冒頭の話に戻ります。

funなんちゃら内で書いたものは、funなんちゃら内でしか使えません。
横断的に使いたい場合は、クラス名の下に書いてあげる。
ただし、findViewByIdは、例外で
setContentViewより後じゃないと実行できません。
てことで、いっそのことこのtvQuestionと、ボタン4つ
合計5つをコピーして
fun incorrectAns()内に貼り付けてください。

  fun incorrectAns(){
        val tvQuestion :TextView = findViewById(R.id.tvQuestion)
        val btn0:Button=findViewById(R.id.btn0)
        val btn1:Button=findViewById(R.id.btn1)
        val btn2:Button=findViewById(R.id.btn2)
        val btn3:Button=findViewById(R.id.btn3)

        //不正解+ボタンの無効化
        tvQuestion.text="不正解!!Game Over"
        btn0.isEnabled = false
        btn1.isEnabled = false
        btn2.isEnabled = false
        btn3.isEnabled = false
    }

なので、慣れるまで少々めんどくさいんですが
これで、こっちの関数でもviewが取得できて
そうすると、ちゃんと正常な色になったので
ちゃんとアクセスできているのがわかります。

2.貼り付け

あとは、この

incorrectAns()

を不正解のコードのところに張り替えていきましょう。
ボタン4つ、4か所ですね。

            }else{
                //不正解+ボタンの無効化
                incorrectAns()
            }

ひとまず、エミュレータを起動して、と行きたいところなんですが
そのまえにもう1つ。
右上に、赤いエラーが出てまして、
あ、この黄色いのは無視していきますからね
赤いやつだけ処理していきます。
クリックすると●行目はprivateにすべきです、って言われているので

アクセス修飾子をprivateと書いてあげましょう。
これで赤いエラーが消えました。

    //10)不正解処理の関数
    private fun incorrectAns(){
        val tvQuestion :TextView = findViewById(R.id.tvQuestion)
        val btn0:Button=findViewById(R.id.btn0)
        val btn1:Button=findViewById(R.id.btn1)
        val btn2:Button=findViewById(R.id.btn2)
        val btn3:Button=findViewById(R.id.btn3)

        //不正解+ボタンの無効化
        tvQuestion.text="不正解!!Game Over"
        btn0.isEnabled = false
        btn1.isEnabled = false
        btn2.isEnabled = false
        btn3.isEnabled = false
    }

こういうのをアクセス修飾子っていうんですが
privateっていうのは、このクラスでしかアクセスできませんよって意味です。
アクセスをコントロールすることで、より安全なプログラムが書けるようになります。

今回もこの関数はこのクラスでしか使ってないので、
Android先生が察知して、privateと書きなさいと
注意喚起された感じです。

てことで色々ありましたけれどもこれで準備が整いましたので
エミュレータを起動して
今まで通り動くというのを確認してみましょう。

正解は問題ないと。
不正解のばあい、
こうですね。ちゃんと不正解処理が実行されているのがわかります。

3.正解処理にアラートダイアログを表示させてみる

それでは最後に、
復習もかねて、
正解した場合の関数を作って
それを、各ボタンの正解のところに貼り付けていきましょう。

まずは関数を用意します。
場所は、不正解の上にでもいっときましょうか。

//11)正解の関数(アラートダイアログを表示)

正解をした時にアラートダイアログを表示してみましょう。

fun correctAns(){
}

で、この関数もこのクラスでしか使わないので
先ほど同様androidスタジオ先生に注意される前に
privateと、アクセス修飾子を付けておけばいいかなと思います。

    //11)正解の関数(アラートダイアログを表示)
    private fun correctAns(){
        
    }

それでは、アラートダイアログを書いていきましょう。
タイトルは正解、
メッセージは、正解の配列を表示させてあげたいので
quizDataの[i]番目、と書きたいところなんですが。

    private fun correctAnswer(){
        AlertDialog.Builder(this)
            .setTitle("正解!")
            .setMessage(quizData[i])
  
    }

ここで、
quizDataと
iにアクセスできません、となっています。

これが冒頭申し上げた、funなんちゃら内で宣言したものは
funなんちゃら内でしか使えない、というルールです。
今はfunction onCreateの{}内に書かれていますので
onCreateメソッドでしか使えませんよとなっています。
横断的に使いたい場合は、クラス名の下に書いてあげましょう。

1つずつやっていきましょう。
まずquizDataから。

quizDataは配列なので、findViewByIdとは違って
クラス名の下に記述しても大丈夫です。

ここ(クラス名の直下)で宣言したものは、各メソッドから横断的に参照が可能ですので
fun onCreateでも使えるし
fun correctAnswerでも使えます。

val quizData = arrayOf(“A0″,”A1″,”A2″,”A3”)

ただしここで宣言する場合はアクセスをコントロールする
アクセス修飾子を付けなければいけません。

anndroidStudio先生もですね
privateじゃねえの?って聞いてきているので
実際このクラスでしか使ってませんので
private val quizData = arrayOf(“A0″,”A1″,”A2″,”A3”)
と書いてあげましょう。

そうすると
quizDataは

fun オンクリエイトメソッドでもアクセスできていますし
新しく作ったfun correctAns()でもアクセスできているのがわかります。

このようにクラス名の下に書いてあげると
横断的に使えるようになります。

ただし、繰り返しますが
findViewById()は注意が必要ですってことですね。

それから同じくiも、アクセスできないようなので

クラス名の下に宣言してあげましょう。ここもちゃんとアクセス修飾子をつけてあげます。
private var i = 0

これでfun correctAnsでも色が正常にもどったので
アクセスできるようになったのがわかります。

では続きを書いていきましょう。

    //11)正解の関数(アラートダイアログを表示) 
   private fun correctAns(){
        AlertDialog.Builder(this)
            .setTitle("正解")
            .setMessage(quizData[i])
            .setPositiveButton("OK",null)
            .show()
    }

これで関数ができました。

あとは、先ほど同様これを掲載したいところに、
correctAns()を貼り付けていけば完成です。

ちょうど
//tvQuestion.text=”正解!!”
が残っているので、これに上書きしていきましょう。

コメントを残す