Klasy i instancje obiektów w Kotlinie

W ramach tych warsztatów stworzysz aplikację na Androida do rzucania kostką. Gdy użytkownik „rzuci kostką”, zostanie wygenerowany losowy wynik. Wynik uwzględnia liczbę ścianek kostki. Na przykład na 6-ściennej kostce mogą wypaść tylko wartości od 1 do 6.

Tak będzie wyglądać gotowa aplikacja.

Aby pomóc Ci skupić się na nowych koncepcjach programowania w tej aplikacji, użyjesz narzędzia do programowania w Kotlinie w przeglądarce, aby utworzyć podstawowe funkcje aplikacji. Program wyświetli wyniki w konsoli. Później zaimplementujesz interfejs użytkownika w Android Studio.

W tym pierwszym ćwiczeniu z programowania utworzysz program w języku Kotlin, który symuluje rzut kostką i wyświetla losową liczbę, tak jak w przypadku prawdziwej kostki.

Wymagania wstępne

  • Jak otwierać, edytować i uruchamiać kod na stronie https://try.kotlinlang.org/
  • Utwórz i uruchom program w Kotlinie, który używa zmiennych i funkcji oraz wyświetla wynik w konsoli.
  • Formatuj liczby w tekście za pomocą szablonu ciągu znaków z notacją ${variable}.

Czego się nauczysz

  • Jak programowo generować liczby losowe, aby symulować rzuty kostką.
  • Jak uporządkować kod, tworząc klasę Dice ze zmienną i metodą.
  • Jak utworzyć instancję obiektu klasy, zmodyfikować jej zmienne i wywołać jej metody.

Co utworzysz

  • Program w języku Kotlin w narzędziu do programowania w Kotlinie w przeglądarce, który może wykonać losowy rzut kostką.

Wymagania

  • komputer z połączeniem internetowym,

Gry często zawierają element losowy. Możesz zdobyć losową nagrodę lub przejść losową liczbę pól na planszy. W życiu codziennym możesz używać losowych cyfr i liter, aby generować bezpieczniejsze hasła.

Zamiast rzucać prawdziwymi kostkami, możesz napisać program, który będzie symulował rzucanie kostkami. Za każdym razem, gdy rzucasz kostką, wynik może być dowolną liczbą z zakresu możliwych wartości. Na szczęście nie musisz tworzyć własnego generatora liczb losowych na potrzeby takiego programu. Większość języków programowania, w tym Kotlin, ma wbudowany sposób generowania liczb losowych. W tym zadaniu użyjesz kodu w języku Kotlin do wygenerowania liczby losowej.

Konfigurowanie kodu startowego

  1. W przeglądarce otwórz stronę https://try.kotlinlang.org/.
  2. Usuń cały istniejący kod w edytorze kodu i zastąp go kodem poniżej. Jest to funkcja main(), z którą pracowaliśmy w poprzednich ćwiczeniach (patrz ćwiczenie Napisz pierwszy program w Kotlinie).
fun main() {

}

Korzystanie z funkcji losowej

Aby rzucić kostką, musisz mieć sposób na przedstawienie wszystkich prawidłowych wartości. W przypadku zwykłej 6-ściennej kostki dopuszczalne wyniki rzutu to: 1, 2, 3, 4, 5 i 6.

Wcześniej dowiedzieliśmy się, że istnieją typy danych, takie jak Int dla liczb całkowitych i String dla tekstu. IntRange to kolejny typ danych, który reprezentuje zakres liczb całkowitych od punktu początkowego do końcowego. IntRange to odpowiedni typ danych do reprezentowania możliwych wartości, jakie można uzyskać podczas rzutu kostką.

  1. W funkcji main() zdefiniuj zmienną jako val o nazwie diceRange. Przypisz ją do liczby IntRange od 1 do 6, która reprezentuje zakres liczb całkowitych, jakie można uzyskać na 6-ściennej kostce do gry.
val diceRange = 1..6

Zakres w języku Kotlin jest oznaczony symbolem 1..6, ponieważ zawiera liczbę początkową, dwie kropki i liczbę końcową (bez spacji między nimi). Inne przykłady zakresów liczb całkowitych to 2..5 dla liczb od 2 do 5 i 100..200 dla liczb od 100 do 200.

Podobnie jak wywołanie funkcji println() powoduje wydrukowanie podanego tekstu, możesz użyć funkcji random(), aby wygenerować i zwrócić losową liczbę z określonego zakresu. Podobnie jak wcześniej możesz zapisać wynik w zmiennej.

  1. W sekcji main() zdefiniuj zmienną jako val o nazwie randomNumber.
  2. Ustaw wartość randomNumber na wynik wywołania funkcji random() w zakresie diceRange, jak pokazano poniżej.
 val randomNumber = diceRange.random()

Zwróć uwagę, że wywołujesz funkcję random() na zmiennej diceRange, używając kropki między zmienną a wywołaniem funkcji. Możesz to odczytać jako „generowanie liczby losowej z diceRange”. Wynik jest następnie przechowywany w zmiennej randomNumber.

  1. Aby wyświetlić losowo wygenerowaną liczbę, użyj notacji formatowania ciągu znaków (nazywanej też „szablonem ciągu znaków”) ${randomNumber}, jak pokazano poniżej.
println("Random number: ${randomNumber}")

Gotowy kod powinien wyglądać tak.

fun main() {
    val diceRange = 1..6
    val randomNumber = diceRange.random()
    println("Random number: ${randomNumber}")
}
  1. Uruchom kod kilka razy. Za każdym razem powinny się wyświetlać dane wyjściowe podobne do tych poniżej, ale z innymi liczbami losowymi.
Random number: 4

Gdy rzucasz kostkami, są one prawdziwymi obiektami w Twoich rękach. Napisany przez Ciebie kod działa bez zarzutu, ale trudno sobie wyobrazić, że chodzi w nim o prawdziwe kostki. Uporządkowanie programu tak, aby przypominał rzeczy, które reprezentuje, ułatwia jego zrozumienie. Fajnie byłoby mieć programowe kostki, którymi można rzucać.

Wszystkie kostki działają w zasadzie tak samo. Mają te same właściwości, np. boki, i zachowują się w ten sam sposób, np. można je rolować. W Kotlinie możesz utworzyć programistyczny schemat kostki, który określa, że kostka ma ścianki i może wylosować liczbę. Ten schemat nazywa się klasą.

Na podstawie tej klasy możesz utworzyć rzeczywiste obiekty kostek do gry, zwane instancjami obiektów. Możesz na przykład utworzyć 12-ścienną lub 4-ścienną kostkę do gry.

Definiowanie klasy Dice

W kolejnych krokach zdefiniujesz nową klasę o nazwie Dice, która będzie reprezentować kostkę do gry.

  1. Aby zacząć od nowa, usuń kod w funkcji main(), tak aby wyglądał jak poniżej.
fun main() {

}
  1. Pod funkcją main() dodaj pusty wiersz, a potem dodaj kod, aby utworzyć klasę Dice. Jak pokazano poniżej, zacznij od słowa kluczowego class, po którym wpisz nazwę klasy, a następnie nawiasy klamrowe otwierający i zamykający. Pozostaw miejsce między nawiasami klamrowymi, aby umieścić kod klasy.
class Dice {

}

W definicji klasy możesz określić co najmniej jedną właściwość klasy za pomocą zmiennych. Prawdziwe kostki mogą mieć różną liczbę ścianek, kolor lub wagę. W tym zadaniu skupisz się na właściwości liczby ścianek kostki.

  1. W klasie Dice dodaj var o nazwie sides, która będzie określać liczbę ścianek kostki. Ustaw wartość sides na 6.
class Dice {
    var sides = 6
}

To wszystko. Masz teraz bardzo prostą klasę reprezentującą kostki.

Tworzenie instancji klasy Dice

Klasa Dice to schemat kostki. Aby mieć w programie rzeczywistą kostkę, musisz utworzyć instancję obiektu Dice. (Jeśli potrzebujesz 3 kostek, utwórz 3 instancje obiektu).

  1. Aby utworzyć instancję obiektu Dice, w funkcji main() utwórz zmienną val o nazwie myFirstDice i zainicjuj ją jako instancję klasy Dice. Zwróć uwagę na nawiasy po nazwie klasy, które oznaczają, że tworzysz nową instancję obiektu z tej klasy.
fun main() {
    val myFirstDice = Dice()
}

Teraz, gdy masz obiekt myFirstDice, czyli rzecz wykonaną na podstawie planu, możesz uzyskać dostęp do jego właściwości. Jedyną właściwością elementu Dice jest jego sides. Do właściwości uzyskujesz dostęp za pomocą „notacji kropkowej”. Aby uzyskać dostęp do właściwości sides obiektu myFirstDice, wywołujesz myFirstDice.sides, co wymawia się „myFirstDice kropka sides”.

  1. Pod deklaracją myFirstDice dodaj instrukcję println(), aby wyświetlić liczbę sidesmyFirstDice..
println(myFirstDice.sides)

Kod powinien wyglądać tak:

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

class Dice {
    var sides = 6
}
  1. Uruchom program. Powinien on wyświetlić liczbę sides zdefiniowaną w klasie Dice.
6

Masz teraz klasę Dice i prawdziwą kostkę myFirstDice z 6 sides.

Rzućmy kostką!

Rzut kostką

Wcześniej używasz funkcji do drukowania warstw ciasta. Rzucanie kostkami to również działanie, które można zaimplementować jako funkcję. Ponieważ wszystkie kostki można rzucać, możesz dodać do klasy Dice funkcję rzutu. Funkcja zdefiniowana w klasie jest też nazywana metodą.

  1. W klasie Dice poniżej zmiennej sides wstaw pusty wiersz, a następnie utwórz nową funkcję rzutu kostką. Zacznij od słowa kluczowego Kotlin fun, po którym wpisz nazwę metody, a następnie nawiasy () i nawiasy klamrowe {}. Możesz zostawić pusty wiersz między nawiasami klamrowymi, aby zrobić miejsce na więcej kodu, jak pokazano poniżej. Klasa powinna wyglądać tak.
class Dice {
    var sides = 6

    fun roll() {

    }
}

Gdy rzucisz sześcienną kostką do gry, otrzymasz losową liczbę z zakresu od 1 do 6.

  1. W metodzie roll() utwórz val randomNumber. Przypisz mu losową liczbę z zakresu 1..6. Użyj notacji kropkowej, aby wywołać random() w zakresie.
val randomNumber = (1..6).random()
  1. Po wygenerowaniu losowej liczby wyświetl ją w konsoli. Ukończona metoda roll() powinna wyglądać tak, jak pokazano poniżej.
fun roll() {
     val randomNumber = (1..6).random()
     println(randomNumber)
}
  1. Aby faktycznie wywołać myFirstDicemain(), wywołaj metodę roll() na obiekcie myFirstDice. Metodę wywołuje się za pomocą „notacji kropkowej”. Aby wywołać metodę roll() obiektu myFirstDice, wpisz myFirstDice.roll(), co wymawia się „myFirstDice kropka roll()”.
myFirstDice.roll()

Gotowy kod powinien wyglądać tak.

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. Uruchom kod. Pod liczbą ścianek powinny się wyświetlić wyniki losowego rzutu kostką. Uruchom kod kilka razy i zwróć uwagę, że liczba ścianek pozostaje taka sama, a wartość rzutu kostką się zmienia.
6
4

Gratulacje! Masz zdefiniowaną klasę Dice ze zmienną sides i funkcją roll(). W funkcji main() utworzono nową instancję obiektu Dice, a następnie wywołano na niej metodę roll(), aby wygenerować liczbę losową.

Obecnie wyświetlasz wartość zmiennej randomNumber w funkcji roll() i działa to świetnie. Czasami jednak bardziej przydatne jest zwrócenie wyniku funkcji do miejsca, z którego została wywołana. Możesz na przykład przypisać wynik działania metody roll() do zmiennej, a potem przesunąć gracza o tę wartość. Zobaczmy, jak to zrobić.

  1. main() zmień wiersz z napisem myFirstDice.roll(). Utwórz val o nazwie diceRoll. Ustaw ją na wartość zwróconą przez metodę roll().
val diceRoll = myFirstDice.roll()

Nie spowoduje to jeszcze żadnych zmian, ponieważ funkcja roll() nie zwraca jeszcze żadnych wartości. Aby ten kod działał zgodnie z oczekiwaniami, funkcja roll() musi zwracać jakąś wartość.

W poprzednich ćwiczeniach z programowania dowiedzieliśmy się, że w przypadku argumentów wejściowych funkcji trzeba określić typ danych. Podobnie musisz określić typ danych zwracanych przez funkcję.

  1. Zmień funkcję roll(), aby określić typ danych, które mają być zwracane. W tym przypadku liczba losowa to Int, więc typ zwracany to Int. Składnia określania typu zwracanego jest następująca: po nazwie funkcji, po nawiasach, dodaj dwukropek, spację, a następnie słowo kluczowe Int dla typu zwracanego funkcji. Definicja funkcji powinna wyglądać tak, jak pokazano poniżej.
fun roll(): Int {
  1. Uruchom ten kod. W widoku problemów pojawi się błąd. Brzmi on:
A ‘return'  expression is required in a function with a block body. 

Zmieniono definicję funkcji, aby zwracała wartość Int, ale system zgłasza, że

kod nie zwraca w rzeczywistości wartości Int. „Blok kodu” lub „treść funkcji” to kod między nawiasami klamrowymi funkcji. Aby naprawić ten błąd, zwróć wartość z funkcji za pomocą instrukcji return na końcu treści funkcji.

  1. roll() usuń instrukcję println() i zastąp ją instrukcją return dla randomNumber. Funkcja roll() powinna wyglądać tak, jak pokazano poniżej.
fun roll(): Int {
     val randomNumber = (1..6).random()
     return randomNumber
}
  1. main() usuń instrukcję drukowania dla ścianek kostki.
  2. Dodaj instrukcję, która wyświetli wartości zmiennych sidesdiceRoll w zdaniu informacyjnym. Gotowa funkcja main() powinna wyglądać podobnie do kodu poniżej.
fun main() {
    val myFirstDice = Dice()
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.sides} sided dice rolled ${diceRoll}!")
}
  1. Uruchom kod. Dane wyjściowe powinny wyglądać tak:
Your 6 sided dice rolled 4!

Oto cały Twój dotychczasowy kod.

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
    }
}

Nie wszystkie kostki mają 6 ścianek. Kostki do gry mają różne kształty i rozmiary: 4, 8, a nawet 120 ścianek.

  1. Dice klasie w roll() metodzie zmień zakodowaną na stałe wartość 1..6 na sides, aby zakres, a tym samym wylosowana liczba losowa, zawsze odpowiadał liczbie ścianek.
val randomNumber = (1..sides).random()
  1. W funkcji main(), poniżej i po wydrukowaniu wyniku rzutu kostką, zmień wartość sides of myFirstDice na 20.
myFirstDice.sides = 20
  1. Skopiuj i wklej poniższy istniejący już komunikat o drukowaniu w miejscu, w którym zmieniono liczbę stron.
  2. Zastąp drukowanie diceRoll drukowaniem wyniku wywołania metody roll() na obiekcie myFirstDice.
println("Your ${myFirstDice.sides} sided dice has rolled a ${myFirstDice.roll()}!")

Program powinien wyglądać tak.

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. Uruchom program. Powinna się wyświetlić wiadomość dotycząca 6-ściennej kostki do gry i druga wiadomość dotycząca 20-ściennej kostki do gry.
Your 6 sided dice rolled 3!
Your 20 sided dice rolled 15!

Klasa reprezentuje rzecz, często fizyczną, w świecie rzeczywistym. W tym przypadku klasa Dice reprezentuje fizyczną kostkę. W rzeczywistości kostki nie mogą zmieniać liczby ścianek. Jeśli chcesz użyć kostki o innej liczbie ścianek, musisz kupić inną kostkę. Z punktu widzenia programowania oznacza to, że zamiast zmieniać właściwość sides istniejącej instancji obiektu Dice, należy utworzyć nową instancję obiektu dice z potrzebną liczbą ścian.

W tym zadaniu zmodyfikujesz klasę Dice, aby podczas tworzenia nowej instancji można było określić liczbę boków. Zmień definicję klasy Dice, aby akceptowała argument określający liczbę boków. Działa to podobnie do funkcji, która może przyjmować argumenty wejściowe.

  1. Zmodyfikuj definicję klasy Dice, aby akceptowała argument całkowity o nazwie numSides. Kod w klasie nie ulegnie zmianie.
class Dice(val numSides: Int) {
   // Code inside does not change.
}
  1. W klasie Dice usuń zmienną sides, ponieważ możesz teraz używać zmiennej numSides.
  2. Ustaw też zakres na numSides.

Klasa Dice powinna wyglądać tak.

class Dice (val numSides: Int) {

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

Jeśli uruchomisz ten kod, zobaczysz wiele błędów, ponieważ musisz zaktualizować tag main(), aby działał ze zmianami w klasie Dice.

  1. main(), aby utworzyć myFirstDice o 6 bokach, musisz teraz przekazać liczbę boków jako argument do klasy Dice, jak pokazano poniżej.
    val myFirstDice = Dice(6)
  1. W instrukcji print zmień sides na numSides.
  2. Następnie usuń kod, który zmienia wartość sides na 20, ponieważ ta zmienna już nie istnieje.
  3. Usuń też stwierdzenie println znajdujące się pod nim.

Funkcja main() powinna wyglądać jak poniższy kod. Jeśli ją uruchomisz, nie powinny wystąpić żadne błędy.

fun main() {
    val myFirstDice = Dice(6)
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.numSides} sided dice rolled ${diceRoll}!")
}
  1. Po wydrukowaniu pierwszego rzutu kostką dodaj kod, który utworzy i wydrukuje drugi obiekt Dice o nazwie mySecondDice z 20 ścianami.
    val mySecondDice = Dice(20)
  1. Dodaj instrukcję drukowania, która przetwarza i drukuje zwróconą wartość.
println("Your ${mySecondDice.numSides} sided dice rolled  ${mySecondDice.roll()}!")
  1. Gotowa funkcja main() powinna wyglądać tak.
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. Uruchom gotowy program. Dane wyjściowe powinny wyglądać tak:
Your 6 sided dice rolled 5!
Your 20 sided dice rolled 7!

Podczas pisania kodu lepiej jest zachować zwięzłość. Możesz pozbyć się zmiennej randomNumber i zwrócić bezpośrednio liczbę losową.

  1. Zmień instrukcję return, aby bezpośrednio zwracała liczbę losową.
    fun roll(): Int {
        return (1..numSides).random()
    }

W drugim poleceniu drukowania umieszczasz wywołanie funkcji pobierania liczby losowej w szablonie ciągu. Możesz pozbyć się zmiennej diceRoll, wykonując tę samą czynność w pierwszej instrukcji drukowania.

  1. Wywołaj myFirstDice.roll() w szablonie ciągu znaków i usuń zmienną diceRoll. Pierwsze 2 wiersze kodu funkcji main() wyglądają teraz tak:
    val myFirstDice = Dice(6)
    println("Your ${myFirstDice.numSides} sided dice rolled ${myFirstDice.roll()}!")
  1. Uruchom kod. Wynik powinien być taki sam.

To ostateczny kod po refaktoryzacji .

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()
    }
}
  • Wywołaj funkcję random() na obiekcie IntRange, aby wygenerować liczbę losową: (1..6).random()
  • Klasy są jak plan obiektu. Mogą mieć właściwości i zachowania zaimplementowane jako zmienne i funkcje.
  • Instancja klasy reprezentuje obiekt, często fizyczny, np. kostkę do gry. Możesz wywoływać działania na obiekcie i zmieniać jego atrybuty.
  • Podczas tworzenia instancji możesz przekazać dane wejściowe do klasy, określając argument definicji klasy. Na przykład: class Dice(val numSides: Int), a następnie utwórz instancję za pomocą Dice(6).
  • Funkcje mogą zwracać wartości. W definicji funkcji określ typ danych, który ma być zwracany, i użyj instrukcji return w treści funkcji, aby coś zwrócić. Na przykład: fun example(): Int { return 5 }

Wykonaj te czynności:

  • Nadaj klasie Dice kolejny atrybut koloru i utwórz wiele instancji kostek o różnej liczbie ścianek i kolorach.
  • Utwórz klasę Coin, nadaj jej możliwość odwracania, utwórz instancję klasy i odwróć kilka monet. Jak użyć funkcji random() z zakresem, aby przeprowadzić rzut monetą?