Классы и экземпляры объектов в Kotlin

В рамках практических работ по этому пути вам предстоит создать Android-приложение Dice Roller. Когда пользователь бросает игральную кость, генерируется случайный результат. Результат учитывает количество граней игральной кости. Например, на шестигранной игральной кости могут выпасть только числа от 1 до 6.

Вот как будет выглядеть финальная версия приложения.

Чтобы сосредоточиться на новых концепциях программирования для этого приложения, вы будете использовать браузерный инструмент программирования Kotlin для создания основных функций приложения. Программа выведет результаты в консоль. Позже вы реализуете пользовательский интерфейс в Android Studio.

В этой первой лабораторной работе вы создадите программу на языке Kotlin, которая имитирует бросание игральных костей и выводит случайное число, как это делает игральная кость.

Предпосылки

  • Как открыть, редактировать и запустить код в https://try.kotlinlang.org/
  • Создайте и запустите программу Kotlin, которая использует переменные и функции, а также выводит результат на консоль.
  • Форматируйте числа в тексте, используя строковый шаблон с обозначением ${variable} .

Чему вы научитесь

  • Как программно генерировать случайные числа для имитации бросков игральных костей.
  • Как структурировать свой код, создав класс Dice с переменной и методом.
  • Как создать экземпляр объекта класса, изменить его переменные и вызвать его методы.

Что вы построите

  • Программа на языке Kotlin в браузерном инструменте программирования Kotlin, которая может выполнять случайный бросок игральной кости.

Что вам нужно

  • Компьютер с подключением к Интернету

В играх часто присутствует элемент случайности. Вы можете выиграть случайный приз или продвинуться на случайное количество шагов по игровому полю. В повседневной жизни вы можете использовать случайные числа и буквы для генерации более надёжных паролей!

Вместо того, чтобы бросать игральные кости, вы можете написать программу, которая имитирует бросок. Каждый раз, когда вы бросаете игральные кости, результатом может быть любое число из диапазона возможных значений. К счастью, вам не нужно создавать собственный генератор случайных чисел для такой программы. Большинство языков программирования, включая Kotlin, имеют встроенные средства для генерации случайных чисел. В этом задании вы будете использовать код Kotlin для генерации случайного числа.

Настройте свой стартовый код

  1. В браузере откройте сайт https://try.kotlinlang.org/ .
  2. Удалите весь существующий код в редакторе кода и замените его кодом ниже. Это функция main() с которой вы работали в предыдущих практических занятиях (см. раздел « Написание первой программы на Kotlin» ).
fun main() {

}

Используйте случайную функцию

Чтобы бросить игральную кость, необходимо представить все возможные значения. Для обычного шестигранного кубика допустимые значения: 1, 2, 3, 4, 5 и 6.

Ранее вы узнали, что существуют такие типы данных, как Int для целых чисел и String для текста. IntRange — это ещё один тип данных, представляющий диапазон целых чисел от начальной до конечной точки. IntRange — подходящий тип данных для представления возможных значений, которые может получить бросок игральной кости.

  1. Внутри функции main() определите переменную типа val с именем diceRange . Присвойте ей значение IntRange от 1 до 6, представляющее диапазон целых чисел, которые может выпасть при бросании шестигранной игральной кости.
val diceRange = 1..6

Диапазон 1..6 можно определить по начальной цифре, двум точкам и конечной цифре (без пробелов). Другие примеры целочисленных диапазонов: 2..5 для чисел от 2 до 5 и 100..200 для чисел от 100 до 200.

Подобно тому, как вызов println() заставляет систему вывести заданный текст, вы можете использовать функцию random() для генерации и возврата случайного числа в заданном диапазоне. Как и раньше, результат можно сохранить в переменной.

  1. Внутри main() определите переменную как val с именем randomNumber .
  2. Присвойте randomNumber значение результату вызова random() для диапазона diceRange , как показано ниже.
 val randomNumber = diceRange.random()

Обратите внимание, что вы вызываете функцию random() для diceRange , используя точку между переменной и вызовом функции. Это можно интерпретировать как «генерация случайного числа из diceRange ». Результат затем сохраняется в переменной randomNumber .

  1. Чтобы увидеть случайно сгенерированное число, используйте обозначение форматирования строки (также называемое «шаблоном строки») ${randomNumber} для его печати, как показано ниже.
println("Random number: ${randomNumber}")

Ваш готовый код должен выглядеть так.

fun main() {
    val diceRange = 1..6
    val randomNumber = diceRange.random()
    println("Random number: ${randomNumber}")
}
  1. Запустите код несколько раз. Каждый раз вы должны увидеть результат, как показано ниже, с разными случайными числами.
Random number: 4

Когда вы бросаете игральные кости, они — реальные предметы в ваших руках. Хотя код, который вы только что написали, работает идеально, сложно представить, что речь идёт о настоящих игральных костях. Организация программы таким образом, чтобы она была больше похожа на то, что она представляет, облегчает её понимание. Поэтому было бы здорово иметь программные игральные кости, которые можно бросать!

Все игральные кости работают по сути одинаково. У них одинаковые свойства, например, грани, и одинаковое поведение, например, их можно бросать. В Kotlin можно создать программную схему игральной кости, которая указывает, что у неё есть грани и что она может выбросить случайное число. Эта схема называется классом .

На основе этого класса можно создавать объекты игральных костей, называемые экземплярами объектов . Например, можно создать 12-гранный или 4-гранный игральный кубик.

Определить класс игральных костей

На следующих этапах вы определите новый класс с именем Dice , который будет представлять собой бросаемую игральную кость.

  1. Чтобы начать заново, очистите код в функции main() чтобы получился код, показанный ниже.
fun main() {

}
  1. Под функцией main() добавьте пустую строку, а затем добавьте код для создания класса Dice . Как показано ниже, начните с ключевого слова class , за которым следует имя класса, а затем открывающая и закрывающая фигурные скобки. Оставьте пробел между фигурными скобками для размещения кода класса.
class Dice {

}

В определении класса вы можете указать одно или несколько свойств класса с помощью переменных. Реальные игральные кости могут иметь количество граней, цвет или вес. В этом задании вы сосредоточитесь на свойстве количества граней игральной кости.

  1. В классе Dice добавьте var sides , которая задаёт количество граней вашей игральной кости. Установите значение sides равным 6.
class Dice {
    var sides = 6
}

Вот и всё. Теперь у вас есть очень простой класс, представляющий игральные кости.

Создать экземпляр класса Dice

С помощью класса Dice у вас есть схема того, что такое игральная кость. Чтобы добавить в программу настоящую игральную кость, вам нужно создать экземпляр объекта Dice . (А если вам нужно три игральные кости, вы создадите три экземпляра объекта.)

  1. Чтобы создать экземпляр объекта Dice , в функции main() создайте val с именем myFirstDice и инициализируйте его как экземпляр класса Dice . Обратите внимание на скобки после имени класса, которые означают, что вы создаёте новый экземпляр объекта из этого класса.
fun main() {
    val myFirstDice = Dice()
}

Теперь, когда у вас есть объект myFirstDice , созданный по чертежу, вы можете получить доступ к его свойствам. Единственное свойство Dice — это его sides . Доступ к свойству осуществляется с помощью «точечной нотации». Таким образом, чтобы получить доступ к свойству sides myFirstDice , нужно вызвать myFirstDice.sides , что произносится как « myFirstDice точка sides ».

  1. Под объявлением myFirstDice добавьте оператор println() для вывода количества sides myFirstDice.
println(myFirstDice.sides)

Ваш код должен выглядеть так.

fun main() {
    val myFirstDice = Dice()
    println(myFirstDice.sides)
}

class Dice {
    var sides = 6
}
  1. Запустите программу, и она должна вывести количество sides определенное в классе Dice .
6

Теперь у вас есть класс Dice и реальная игральная кость myFirstDice с 6 sides .

Давайте бросим кости!

Бросай кости

Ранее вы использовали функцию для печати слоев торта. Бросок игральных костей — это тоже действие, которое можно реализовать как функцию. А поскольку все игральные кости можно бросать, вы можете добавить для этого функцию в класс Dice . Функция, определённая внутри класса, также называется методом .

  1. В классе Dice под переменной sides вставьте пустую строку и создайте новую функцию для броска игральной кости. Начните с ключевого слова Kotlin fun , затем укажите имя метода, скобки () и открывающие и закрывающие фигурные скобки {} . Вы можете оставить пустую строку между фигурными скобками, чтобы освободить место для кода, как показано ниже. Ваш класс должен выглядеть следующим образом.
class Dice {
    var sides = 6

    fun roll() {

    }
}

При бросании шестигранного игрального кубика выпадает случайное число от 1 до 6.

  1. Внутри метода roll() создайте переменную val randomNumber . Присвойте ей случайное число в диапазоне 1..6 Для вызова метода random() для этого диапазона используйте точку.
val randomNumber = (1..6).random()
  1. После генерации случайного числа выведите его на консоль. Готовый метод roll() должен выглядеть так, как показано ниже.
fun roll() {
     val randomNumber = (1..6).random()
     println(randomNumber)
}
  1. Чтобы бросить кубик myFirstDice , в main() вызовите метод roll() для кубика myFirstDice . Вызов метода осуществляется с использованием «точечной нотации». Таким образом, чтобы вызвать метод roll() для myFirstDice , введите 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)
    }
}
  1. Запустите код! Вы должны увидеть результат случайного броска кубика под числом граней. Запустите код несколько раз и обратите внимание, что число граней остаётся неизменным, а значение броска кубика меняется.
6
4

Поздравляем! Вы определили класс Dice с переменной sides и функцией roll() . В функции main() вы создали новый экземпляр объекта Dice , а затем вызвали для него метод roll() для генерации случайного числа.

Сейчас вы выводите значение randomNumber в функции roll() , и это отлично работает! Но иногда полезнее вернуть результат функции в функцию, вызвавшую эту функцию. Например, можно присвоить результат метода roll() переменной, а затем переместить игрока на это значение! Давайте посмотрим, как это делается.

  1. В main() измените строку myFirstDice.roll() . Создайте переменную val с именем diceRoll . Установите её равной значению, возвращаемому методом roll() .
val diceRoll = myFirstDice.roll()

Это пока ничего не делает, потому что roll() пока ничего не возвращает. Чтобы этот код работал как задумано, roll() должен что-то возвращать.

В предыдущих практических занятиях вы узнали, что необходимо указывать тип данных для входных аргументов функций. Аналогично, необходимо указывать тип данных для данных, возвращаемых функцией.

  1. Измените функцию roll() , чтобы указать тип возвращаемых данных. В данном случае случайное число — Int , поэтому тип возвращаемого значения — Int . Синтаксис указания типа возвращаемого значения следующий: после имени функции, после скобок добавьте двоеточие, пробел и ключевое слово Int для типа возвращаемого значения функции. Определение функции должно выглядеть так, как показано ниже.
fun roll(): Int {
  1. Запустите этот код. В окне «Проблемы» вы увидите сообщение об ошибке:
A ‘return'  expression is required in a function with a block body. 

Вы изменили определение функции, чтобы она возвращала Int , но система жалуется, что ваша

Код на самом деле не возвращает значение типа Int . «Тело блока» или «тело функции» — это код, заключенный в фигурные скобки. Вы можете исправить эту ошибку, вернув значение из функции с помощью оператора return в конце тела функции.

  1. В функции roll() удалите оператор println() и замените его оператором return для randomNumber . Ваша функция roll() должна выглядеть так, как показано ниже.
fun roll(): Int {
     val randomNumber = (1..6).random()
     return randomNumber
}
  1. В main() удалите оператор печати для сторон кубика.
  2. Добавьте оператор для вывода значений sides и diceRoll в информативное предложение. Готовая функция main() должна выглядеть примерно так, как показано ниже.
fun main() {
    val myFirstDice = Dice()
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.sides} sided dice rolled ${diceRoll}!")
}
  1. Запустите свой код, и результат должен быть таким.
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
    }
}

Не у всех игральных костей шесть граней! Игральные кости бывают разных форм и размеров: четырёхгранные, восьмигранные и даже до 120 граней!

  1. В классе Dice , в методе roll() измените жестко заданные 1..6 , чтобы вместо этого использовались sides , так диапазон и, следовательно, случайное выброшенное число всегда будут соответствовать количеству граней.
val randomNumber = (1..sides).random()
  1. В функции main() , ниже, после печати результата броска кубика, измените sides моего FirstDice , установив значение 20.
myFirstDice.sides = 20
  1. Скопируйте и вставьте существующий оператор печати ниже после того места, где вы изменили количество сторон.
  2. Замените печать diceRoll печатью результата вызова метода roll() на myFirstDice .
println("Your ${myFirstDice.sides} sided dice has rolled a ${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
    }
}
  1. Запустите программу, и вы увидите сообщение для 6-гранного кубика и второе сообщение для 20-гранного кубика.
Your 6 sided dice rolled 3!
Your 20 sided dice rolled 15!

Идея класса заключается в представлении чего-либо, часто физического в реальном мире. В данном случае класс Dice представляет собой физическую игральную кость. В реальном мире игральные кости не могут менять количество граней. Если вам нужно другое количество граней, вам нужно получить другую игральную кость. Программно это означает, что вместо изменения свойства sides существующего экземпляра объекта Dice необходимо создать новый экземпляр объекта Dice с нужным количеством граней.

В этом задании вам предстоит изменить класс Dice , чтобы можно было указывать количество граней при создании нового экземпляра. Измените определение класса Dice так, чтобы оно принимало аргумент, указывающий количество граней. Это похоже на то, как функция принимает аргументы для ввода.

  1. Измените определение класса Dice так, чтобы оно принимало целочисленный аргумент с именем numSides . Код внутри вашего класса не изменится.
class Dice(val numSides: Int) {
   // Code inside does not change.
}
  1. Внутри класса Dice удалите переменную sides , так как теперь можно использовать numSides .
  2. Также исправьте диапазон, используя numSides .

Ваш класс Dice должен выглядеть следующим образом.

class Dice (val numSides: Int) {

    fun roll(): Int {
        val randomNumber = (1..numSides).random()
        return randomNumber
    }
}

Если вы запустите этот код, вы увидите множество ошибок, поскольку вам необходимо обновить main() для работы с изменениями в классе Dice .

  1. В main() для создания myFirstDice с 6 гранями необходимо передать количество граней в качестве аргумента классу Dice , как показано ниже.
    val myFirstDice = Dice(6)
  1. В операторе печати измените sides на numSides .
  2. Ниже удалите код, который изменяет sides на 20, поскольку эта переменная больше не существует.
  3. Удалите также оператор println под ним.

Ваша функция main() должна выглядеть так, как показано ниже, и если вы запустите ее, не должно возникнуть ошибок.

fun main() {
    val myFirstDice = Dice(6)
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.numSides} sided dice rolled ${diceRoll}!")
}
  1. После печати первого броска игральной кости добавьте код для создания и печати второго объекта Dice с именем mySecondDice и 20 гранями.
    val mySecondDice = Dice(20)
  1. Добавьте оператор печати, который прокручивает и печатает возвращаемое значение.
println("Your ${mySecondDice.numSides} sided dice rolled  ${mySecondDice.roll()}!")
  1. Готовая функция 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
    }
}
  1. Запустите готовую программу, и ваш вывод должен выглядеть примерно так.
Your 6 sided dice rolled 5!
Your 20 sided dice rolled 7!

При написании кода важно быть лаконичным. Вы можете избавиться от переменной randomNumber и напрямую возвращать случайное число.

  1. Измените оператор return так, чтобы он напрямую возвращал случайное число.
    fun roll(): Int {
        return (1..numSides).random()
    }

Во втором операторе печати вызов для получения случайного числа помещается в шаблон строки. Вы можете избавиться от переменной diceRoll , сделав то же самое в первом операторе печати.

  1. Вызовите myFirstDice.roll() в шаблоне строки и удалите переменную diceRoll . Первые две строки кода main() теперь выглядят так.
    val myFirstDice = Dice(6)
    println("Your ${myFirstDice.numSides} sided dice rolled ${myFirstDice.roll()}!")
  1. Запустите свой код, и в результатах не должно быть никакой разницы.

Это ваш окончательный код после рефакторинга .

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()
    }
}
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 {
        return (1..numSides).random()
    }
}
  • Вызовите функцию random() для IntRange , чтобы сгенерировать случайное число: (1..6).random()
  • Классы подобны чертежам объектов. Они могут иметь свойства и поведение, реализованные в виде переменных и функций.
  • Экземпляр класса представляет объект, часто физический, например игральную кость. Вы можете вызывать действия над объектом и изменять его атрибуты.
  • Вы можете передать входные данные классу при создании экземпляра, указав аргумент для определения класса. Например: class Dice(val numSides: Int) , а затем создать экземпляр с помощью Dice(6) .
  • Функции могут возвращать данные. Укажите тип возвращаемых данных в определении функции и используйте оператор return в теле функции, чтобы вернуть данные. Например: fun example(): Int { return 5 }

Сделайте следующее:

  • Добавьте к классу Dice еще один атрибут цвета и создайте несколько экземпляров игральных костей с разным количеством граней и цветов!
  • Создайте класс Coin , дайте ему возможность подбрасывать монеты, создайте экземпляр класса и подбрасывайте монеты! Как бы вы использовали функцию random() с диапазоном для подбрасывания монеты?