1. 始める前に
この Codelab では、Dice Roller Android アプリを作成します。ユーザーがサイコロを振る(rolls the dice)と、ランダムな結果が生成されます。結果ではサイコロの面数が考慮されます。たとえば、6 面のサイコロを振ると 1~6 の値のみが出ます。
完成したアプリの外観は次のようになります。
このアプリで新しいプログラミングの概念に集中できるように、ブラウザベースの Kotlin プログラミング ツールを使用してアプリの主要な機能を作成します。プログラムによって、結果がコンソールに出力されます。後で、Android Studio にてユーザー インターフェースを実装します。
この最初の Codelab では、サイコロ投げをシミュレートして、現実のサイコロと同じように乱数を出力する Kotlin プログラムを作成します。
前提条件
- https://developer.android.com/training/kotlinplayground でコードを開き、編集し、実行できる。
- 変数と関数を使用し、結果をコンソールに出力する Kotlin プログラムを作成、実行できる。
${variable}
表記の文字列テンプレートを使用して、テキスト内の数字をフォーマットできる。
学習内容
- プログラムで乱数を生成し、サイコロ投げをシミュレートする方法。
- 変数とメソッドを含む
Dice
クラスを作成してコードを構築する方法。 - クラスのオブジェクト インスタンスを作成し、その変数を変更し、メソッドを呼び出す方法。
作成するアプリの概要
- ブラウザベースの Kotlin プログラミング ツールを使用して、ランダムなサイコロ投げを実行できる Kotlin プログラムを作成します。
必要なもの
- インターネットに接続されているパソコン
2. サイコロを振って乱数を出す
多くのゲームには、ランダムな要素が含まれています。たとえば、ランダムな賞金を獲得したり、ゲームボードでランダムな数だけコマを進めたりする場合などです。日常生活でも、ランダムな数字と文字を使うことで、より安全なパスワードを設定できます。
ここでは現実のサイコロを振るのではなく、サイコロ投げをシミュレートするプログラムを作成します。サイコロを振るたびに、可能な値の範囲内で数字がランダムに出るようにします。幸いなことに、このようなプログラムのために独自の乱数ジェネレータを構築する必要はありません。Kotlin を含むほとんどのプログラミング言語には、乱数を生成するための機能が用意されています。このタスクでは、Kotlin コードを使用して乱数を生成します。
スターター コードを設定する
- ブラウザで、ウェブサイト https://developer.android.com/training/kotlinplayground にアクセスします。
- コードエディタ内の既存のコードをすべて削除し、次のコードに置き換えます。これは前の Codelab で使用した
main()
関数です。
fun main() {
}
ランダム関数を使用する
サイコロを振るには、すべての有効な出目の値を表すための方法が必要です。通常の 6 面サイコロの場合、有効な値は 1、2、3、4、5、6 です。
以前の Codelab で、整数の Int
やテキストの String
などのデータ型があることを学びました。IntRange
もデータ型であり、これは始点から終点までの整数の範囲を表します。IntRange
は、サイコロ投げで出る可能性のある目の値を表すのに適切なデータ型です。
main()
関数内で、変数をdiceRange
というval
として定義します。この変数に、1 から 6 までのIntRange
を代入します。これは、6 面のサイコロを振って出る可能性のある整数の範囲を表します。
val diceRange = 1..6
1..6
は Kotlin の範囲です。これは、始点の数字、2 つのドット、終点の数字で構成され、間にスペースがないことから判断できます。整数の範囲の他の例として、2~5 の範囲は 2..5
、100~200 の範囲は 100..200
となります。
println()
を呼び出すことで特定のテキストを出力できるように、random()
という関数を使用して、特定の範囲の乱数を生成して返すことができます。前と同様に、結果を変数に格納できます。
main()
内で、変数をrandomNumber
というval
として定義します。- 以下に示すように、
diceRange
の範囲でrandom()
を呼び出した結果の値がrandomNumber
に格納されるようにします。
val randomNumber = diceRange.random()
変数 diceRange
と関数 random()
の間にピリオド(ドット)を使用して呼び出していることに注意してください。これは、「diceRange
から乱数を生成する」と読むことができます。呼び出しの結果は randomNumber
変数に格納されます。
- ランダムに生成された数字を表示するには、文字列の形式表記(「文字列テンプレート」とも呼ばれます)である
${randomNumber}
を使用して出力します。
println("Random number: ${randomNumber}")
完成したコードは次のようになります。
fun main() {
val diceRange = 1..6
val randomNumber = diceRange.random()
println("Random number: ${randomNumber}")
}
- コードを複数回実行してみてください。出力は次のようになり、実行ごとにランダムな数字が表示されます。
Random number: 4
3.Dice クラスを作成する
実際にサイコロを振るときには、サイコロは手の中に現実の物体(オブジェクト)として存在します。さきほど作成したコードは問題なく動作しますが、これが現実のサイコロの動作を表現したコードであるとは想像しにくいでしょう。表現する対象に似せてプログラムを構成することで、プログラムを理解しやすくなります。そこで、ここではプログラムでサイコロを作成し、実際にサイコロ投げができるようにします。
すべてのサイコロは、基本的に同じように機能します。「面がある」という特性(プロパティ)と、「振ることができる」という動作はいずれも共通しています。この「サイコロには面があり、振ると乱数の目が出る」というサイコロの設計図を、Kotlin でプログラムを使って作成できます。この設計図がクラスと呼ばれるものです。
そのクラスから、実際のサイコロのオブジェクトを作成できます。これをオブジェクト インスタンスと呼びます。たとえば、12 面のサイコロや 4 面のサイコロを作成することが可能です。
Dice クラスを定義する
次の手順では、Dice
という名前の新しいクラスを定義して、振ることのできるサイコロを表現します。
- 新しいコードを作成するには、
main()
関数のコードを削除して、最終的にコードを次のようにします。
fun main() {
}
- この
main()
関数の下に空白行を追加し、Dice
クラスを作成するためのコードを追加します。次に示すように、キーワードclass
、クラス名、左中かっこ、右中かっこの順に記述します。中かっこの間には、クラスのコードを記述するためのスペースを残しておきます。
class Dice {
}
クラスの定義内で、変数を使用してクラスのプロパティを 1 つまたは複数指定できます。現実のサイコロには複数の面があり、色や重量もあります。このタスクでは、サイコロの面数というプロパティに焦点を当てます。
Dice
クラス内に、サイコロの面数を示すsides
というvar
を追加します。sides
を 6 に設定します。
class Dice {
var sides = 6
}
これで、サイコロを表す非常にシンプルなクラスを作成できました。
Dice クラスのインスタンスを作成する
この Dice
クラスを、サイコロの設計図として使用できます。プログラムで実際にサイコロを使うには、Dice
のオブジェクト インスタンスを作成する必要があります(3 つのサイコロが必要な場合は、3 つのオブジェクト インスタンスを作成します)。
Dice
のオブジェクト インスタンスを作成するには、main()
関数内でmyFirstDice
というval
を作成し、Dice
クラスのインスタンスとして初期化します。クラス名の後のかっこは、クラスから新しいオブジェクト インスタンスを作成することを示しています。
fun main() {
val myFirstDice = Dice()
}
設計図から作成した myFirstDice
オブジェクトができたら、そのプロパティにアクセスできます。Dice
の唯一のプロパティは sides
です。プロパティにアクセスするには「ドット表記法」を使用します。したがって、myFirstDice
の sides
プロパティにアクセスするには、myFirstDice.sides
(「myFirstDice
ドット sides
」と読みます)を呼び出します。
myFirstDice
の宣言の下にprintln()
ステートメントを追加して、myFirstDice.
のsides
の数を出力します。
println(myFirstDice.sides)
コードの内容は次のようになります。
fun main() {
val myFirstDice = Dice()
println(myFirstDice.sides)
}
class Dice {
var sides = 6
}
- プログラムを実行すると、
Dice
クラスで定義されたsides
の数が出力されます。
6
これで、Dice
クラスと、6 つの sides
を持つ実際のサイコロ myFirstDice
が作成されました。
それではサイコロを振ってみましょう。
サイコロを振る
以前、関数を使用して、ケーキレイヤを出力するアクションを実行しました。サイコロ投げも同様に、関数として実装できるアクションです。また、すべてのサイコロは振ることができるため、Dice
クラス内にその関数を追加できます。クラス内で定義される関数はメソッドとも呼ばれます。
Dice
クラス内で、sides
変数の下に空白行を挿入し、サイコロを振るための新しい関数を作成します。Kotlin キーワードfun
、メソッド名、かっこ()
、左中かっこと右かっこ{}
の順に記述します。以下に示すように、中かっこの間に空白行を入れておくと、追加のコード用のスペースを確保できます。クラスは次のようになります。
class Dice {
var sides = 6
fun roll() {
}
}
6 面のサイコロを振ると、1~6 の乱数が生成されます。
roll()
メソッド内でval randomNumber
を作成し、1..6
の範囲の乱数を割り当てます。ドット表記を使用して、この範囲でrandom()
を呼び出します。
val randomNumber = (1..6).random()
- 乱数を生成したら、コンソールに出力します。完成した
roll()
メソッドは次のようになります。
fun roll() {
val randomNumber = (1..6).random()
println(randomNumber)
}
- 実際に
myFirstDice
を振るには、main()
で、myFirstDice
のroll()
メソッドを呼び出します。メソッドを呼び出す際は「ドット表記法」を使用するため、myFirstDice
のroll()
メソッドを呼び出すには、myFirstDice.roll()
(「myFirstDice
ドットroll()
」と読みます)と記述します。
myFirstDice.roll()
コードは次のようになります。
fun main() {
val myFirstDice = Dice()
println(myFirstDice.sides)
myFirstDice.roll()
}
class Dice {
var sides = 6
fun roll() {
val randomNumber = (1..6).random()
println(randomNumber)
}
}
- コードを実行してみましょう。面数の下にランダムな出目の結果が表示されます。コードを複数回実行すると、面数がそのままで、出目の値が変わることがわかります。
6 4
これで、sides
変数と roll()
関数を持つ Dice
クラスを定義できました。main()
関数で新しい Dice
オブジェクト インスタンスを作成し、その roll()
メソッドを呼び出して乱数を生成しました。
4. サイコロの出目の値を返す
この時点で、roll()
関数で randomNumber
の値を問題なく出力できていますが、関数の呼び出し元に関数の結果を返す方が便利な場合もあります。たとえば、roll()
メソッドの結果を変数に割り当てて、出目の数だけプレーヤーを移動させるといった場合などです。その方法を確認してみましょう。
main()
で、myFirstDice.roll()
と記述されている行を変更し、diceRoll
というval
を作成します。roll()
メソッドで返される値を割り当てます。
val diceRoll = myFirstDice.roll()
roll()
はまだ何も返していないため、このように設定しても何も起こりません。このコードが意図したとおりに動作するには、roll()
がなんらかの値を返す必要があります。
前の Codelab では、関数に渡す入力引数のデータ型を指定する必要があることを学びました。同様に、関数が返すデータのデータ型を指定する必要があります。
roll()
関数を変更して、返されるデータのデータ型を指定します。この場合、乱数はInt
であるため、戻り値の型はInt
になります。戻り値の型を指定する構文は、関数名の後のかっこの後に、コロン、スペースと続けて、関数の戻り値の型としてInt
キーワードを追加します。関数定義は次のコードのようになります。
fun roll(): Int {
- このコードを実行します。[Problems View] に次のエラーが表示されます。
A ‘return' expression required in a function with a block body (‘{...}')
Int
を返すように関数定義を変更しましたが、コードが実際には Int
を返さないため、エラーとなっています。「ブロック本体(block body)」または「関数本体(function body)」とは、関数における中かっこの間のコードを指します。このエラーを修正するには、関数本体の末尾で return
ステートメントを使用して関数から値を返します。
roll()
でprintln()
ステートメントを削除し、randomNumber
のreturn
ステートメントに置き換えます。roll()
関数は次のコードのようになります。
fun roll(): Int {
val randomNumber = (1..6).random()
return randomNumber
}
main()
で、サイコロの面数を出力する print ステートメントを削除します。- わかりやすい文で
sides
とdiceRoll
の値を出力するステートメントを追加します。完成したmain()
関数は次のコードのようになります。
fun main() {
val myFirstDice = Dice()
val diceRoll = myFirstDice.roll()
println("Your ${myFirstDice.sides} sided dice rolled ${diceRoll}!")
}
- コードを実行すると、次のように出力されます。
Your 6 sided dice rolled 4!
ここまでで、コードは次のようになりました。
fun main() {
val myFirstDice = Dice()
val diceRoll = myFirstDice.roll()
println("Your ${myFirstDice.sides} sided dice rolled ${diceRoll}!")
}
class Dice {
var sides = 6
fun roll(): Int {
val randomNumber = (1..6).random()
return randomNumber
}
}
5. サイコロの面数を変更する
サイコロは 6 面とは限りません。4 面や 8 面、最大で 120 面と、さまざまな形状とサイズのサイコロがあります。
Dice
クラスのroll()
メソッドで、ハードコードされた1..6
を、sides
を使用するように変更します。これにより、面数に応じた範囲が正しく設定され、適切な乱数が出るようになります。
val randomNumber = (1..sides).random()
main()
関数の、出目を出力するコードの下で、myFirstDice
のsides
を 20 に変更します。
myFirstDice.sides = 20
- さきほど面数を変更したコードの下に、以下の既存の print ステートメントをコピーして貼り付けます。
- 出力対象の
diceRoll
を、myFirstDice
のroll()
メソッドの呼び出し結果に置き換えます。
println("Your ${myFirstDice.sides} sided dice rolled ${myFirstDice.roll()}!")
プログラムは次のようになります。
fun main() {
val myFirstDice = Dice()
val diceRoll = myFirstDice.roll()
println("Your ${myFirstDice.sides} sided dice rolled ${diceRoll}!")
myFirstDice.sides = 20
println("Your ${myFirstDice.sides} sided dice rolled ${myFirstDice.roll()}!")
}
class Dice {
var sides = 6
fun roll(): Int {
val randomNumber = (1..sides).random()
return randomNumber
}
}
- プログラムを実行すると、6 面のサイコロに関するメッセージと、20 面のサイコロに関するメッセージが表示されます。
Your 6 sided dice rolled 3! Your 20 sided dice rolled 15!
6. サイコロをカスタマイズする
クラスの概念は、もの(多くの場合、現実世界に物理的に存在するもの)を表すために使用されます。このプログラムの場合、Dice
クラスは物理的なサイコロを表しています。現実の世界では、サイコロの面数を変えることはできません。面数を変えるには、別のサイコロを入手する必要があります。プログラムでも同様に、既存の Dice
オブジェクト インスタンスの面のプロパティを変更するのではなく、必要な面数を持つ dice オブジェクト インスタンスを新しく作成する必要があります。
このタスクでは、新しいインスタンスの作成時に面数を指定できるように Dice
クラスを変更します。Dice
クラスの定義を変更して、面数を指定できるようにします。これは、関数が入力引数を受け入れるようにするための方法と似ています。
numSides
という整数を受け入れるようにDice
クラスの定義を変更します。クラス内のコードは変更されません。
class Dice(val numSides: Int) {
// Code inside does not change.
}
numSides
を使用できるようになったため、Dice
クラス内のsides
変数を削除します。- また、範囲も
numSides
を使用するように修正します。
Dice
クラスは次のようになります。
class Dice (val numSides: Int) {
fun roll(): Int {
val randomNumber = (1..numSides).random()
return randomNumber
}
}
このコードを実行すると多数のエラーが表示されます。これは、Dice
クラスの変更に対応するように main()
を更新する必要があるためです。
main()
で 6 面のmyFirstDice
を作成するには、次のようにDice
クラスに対する引数として面数を指定する必要があります。
val myFirstDice = Dice(6)
- print ステートメントの
sides
をnumSides
に変更します。 - その下にある、
sides
を 20 に変更するコードを削除します。この変数がもはや存在しないためです。 - その下の
println
ステートメントも削除します。
main()
関数は次のようなコードになります。実行してもエラーにならないはずです。
fun main() {
val myFirstDice = Dice(6)
val diceRoll = myFirstDice.roll()
println("Your ${myFirstDice.numSides} sided dice rolled ${diceRoll}!")
}
- 最初の出目の出力の後に、
mySecondDice
という 2 つ目のDice
オブジェクト(20 面)を作成および出力するコードを追加します。
val mySecondDice = Dice(20)
- サイコロを振って返された値を出力する print ステートメントを追加します。
println("Your ${mySecondDice.numSides} sided dice rolled ${mySecondDice.roll()}!")
- 完成した
main()
関数は次のようになります。
fun main() {
val myFirstDice = Dice(6)
val diceRoll = myFirstDice.roll()
println("Your ${myFirstDice.numSides} sided dice rolled ${diceRoll}!")
val mySecondDice = Dice(20)
println("Your ${mySecondDice.numSides} sided dice rolled ${mySecondDice.roll()}!")
}
class Dice (val numSides: Int) {
fun roll(): Int {
val randomNumber = (1..numSides).random()
return randomNumber
}
}
- 完成したプログラムを実行すると、出力は次のようになります。
Your 6 sided dice rolled 5! Your 20 sided dice rolled 7!
7. 適切なコーディング慣習を採用する
コードを記述する際には、できるだけ簡潔にすることをおすすめします。randomNumber
変数を削除して、乱数を直接返すことができます。
return
ステートメントを変更して、乱数を直接返すようにします。
fun roll(): Int {
return (1..numSides).random()
}
2 つ目の print ステートメントでは、文字列テンプレートに乱数を取り込むための呼び出しを記述しています。1 つ目の print ステートメントでも同じようにすることで、diceRoll
変数を削除できます。
- 文字列テンプレートで
myFirstDice.roll()
を呼び出し、diceRoll
変数を削除します。main()
コードの最初の 2 行は次のようになります。
val myFirstDice = Dice(6)
println("Your ${myFirstDice.numSides} sided dice rolled ${myFirstDice.roll()}!")
- コードを実行し、出力結果に違いが生じないことを確認します。
リファクタリング後の最終的なコードは次のようになります。
fun main() {
val myFirstDice = Dice(6)
println("Your ${myFirstDice.numSides} sided dice rolled ${myFirstDice.roll()}!")
val mySecondDice = Dice(20)
println("Your ${mySecondDice.numSides} sided dice rolled ${mySecondDice.roll()}!")
}
class Dice (val numSides: Int) {
fun roll(): Int {
return (1..numSides).random()
}
}
8. 解答コード
fun main() {
val myFirstDice = Dice(6)
println("Your ${myFirstDice.numSides} sided dice rolled ${myFirstDice.roll()}!")
val mySecondDice = Dice(20)
println("Your ${mySecondDice.numSides} sided dice rolled ${mySecondDice.roll()}!")
}
class Dice (val numSides: Int) {
fun roll(): Int {
return (1..numSides).random()
}
}
9. 概要
IntRange
でrandom()
関数を呼び出して、乱数を生成します((1..6).random()
)。- クラスはオブジェクトの設計図のようなものです。オブジェクトの特性(プロパティ)と動作は、変数や関数として実装できます。
- クラスのインスタンスはオブジェクト(多くの場合、サイコロなどの物体)を表します。オブジェクトのアクションを呼び出して、属性を変更できます。
- インスタンスを作成するときに、クラスに値を指定できます。たとえば、
class Dice(val numSides: Int)
のように指定してから、Dice(6)
でインスタンスを作成します。 - 関数は、なんらかの値を返すことができます。関数定義で返されるデータ型を指定し、関数本体で
return
ステートメントを使用してなんらかの値を返します(例:fun example(): Int { return 5 }
)。
10. 詳細
11. 自習用練習問題
次のことを行います。
Dice
クラスにさらに色(color)の属性を定義して、面数と色が異なる複数のサイコロのインスタンスを作成します。Coin
クラスを作成して、コイントスができるようにします。クラスのインスタンスを作成し、実際にコイントスをやってみましょう。一定の範囲を持つrandom()
関数をどのように使用すればコイントスを実現できるか考えてみてください。