1. 事前準備
在本課程的程式碼研究室,您會建構 Dice Roller Android 應用程式。使用者「擲骰子」時,系統會隨機產生結果。系統在產生結果時會考慮到骰子的面數。舉例來說,6 面的骰子只能得出 1 到 6 的值。
最終應用程式應如下所示。
為了協助您專注瞭解這個應用程式的最新程式設計概念,您會使用 Kotlin 程式設計工具為基礎的瀏覽器,來建立核心應用程式功能。該程式會將結果輸出至主控台。您之後會在 Android Studio 中實作使用者介面。
在第一個程式碼研究室中,您要建立 Kotlin 程式來模擬擲骰子的動作,並輸出隨機數字,就像實際擲骰子一樣。
必要條件
- 瞭解如何在 https://developer.android.com/training/kotlinplayground 中開啟、編輯及執行程式碼
- 建立並執行使用變數和函式的 Kotlin 程式,然後將結果輸出至主控台。
- 使用採用
${variable}
標記法的字串範本,來設定文字內的數字格式。
課程內容
- 如何透過程式輔助方式產生隨機數字來模擬擲骰子的動作。
- 如何透過建立含有變數和方法的
Dice
類別來建構程式碼。 - 如何建立類別的物件例項、修改其變數,以及呼叫其方法。
建構項目
- 使用 Kotlin 程式工具為基礎的瀏覽器,建立可執隨機擲骰子的 Kotlin 程式。
軟硬體需求
- 已連上網際網路的電腦
2. 投擲隨機數字
遊戲通常都有隨機的特性,讓您贏得隨機獎勵,或是在棋盤上隨機移動步數。在日常生活中,您可以使用隨機數字和字母來產生更安全的密碼!
您可以編寫程式模擬擲骰子的動作,而不必真的擲骰子。每次擲骰子時,可能值範圍內的任何數字都可以是結果。幸運的是,你並不需要為這類程式建構自己的隨機數字產生器。包括 Kotlin 在內的多數程式設計語言都內建產生隨機數字的方法。在這項工作中,您會使用 Kotlin 程式碼產生一個隨機數字。
設定範例程式碼
- 在瀏覽器中開啟網站:https://developer.android.com/training/kotlinplayground。
- 刪除程式碼編輯器中所有現有程式碼,並以下方程式碼取代。這是您在之前程式碼研究室中使用的
main()
函式。
fun main() {
}
使用隨機函式
如要擲骰子,您需要設法呈現所有有效的骰子值。一般的 6 面骰子,可接受的結果有 1、2、3、4、5 和 6。
你在先前的課程中已學會資料類型,例如 Int
代表整數,String
代表文字。IntRange
是另一種資料類型,代表從起點到終點的整數範圍。IntRange
是適合的資料類型,用於代表骰子可能產生的值。
- 在
main()
函式中,請將變數定義為名為diceRange
的val
。請將其指派給 1 到 6 的IntRange
,代表 6 面骰子可以擲出的整數範圍。
val diceRange = 1..6
可以看出,1..6
是 Kotlin 範圍,因為它包含開始數字、兩個點,後面接著是結束號碼數字 (中間沒有空格)。以下列舉其他整數範圍的例子:2..5
代表數字 2 到 5,100..200
代表數字 100 到 200。
與呼叫 println()
會指示系統列印指定文字的做法類似,您可以使用名為 random()
的函式,產生並傳回指定範圍的隨機數字。和之前一樣,您可以將結果儲存在變數中。
- 在
main()
中,將變數定義為名為randomNumber
的val
。 - 請為
randomNumber
設定對diceRange
範圍呼叫random()
的結果,如下所示。
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. 建立骰子類別
擲骰子時,骰子是你手中的真實物品。您剛才編寫的程式碼雖然可以正常運作,但很難讓人聯想到真正的骰子。編寫程式時,如果讓程式更貼近所代表的事物,會讓人更容易理解。如果能用程式設計方式,建立一個可以投擲的骰子,那就太棒了!
所有骰子的運作原理都大同小異。它們有相同的屬性 (例如有多個面),而且有相同的行為 (例如可以投擲)。在 Kotlin 中,您可以建立骰子的程式輔助藍圖,指出骰子有多個面,而且可以擲出隨機數字。此藍圖稱為類別。
接下來,就可以透過該類別建立實際的骰子物件,稱為「物件執行個體」。例如,您可以建立有 12 個面或 4 個面的骰子。
定義骰子類別
在下列步驟中,您會定義名為 Dice
的新類別來代表可投擲的骰子。
- 為了重新開始,請清除
main()
函式中的程式碼,最終程式碼應如下所示。
fun main() {
}
- 在這個
main()
函式下方,新增空白行,然後新增程式碼來建立Dice
類別。如下所示,請先輸入關鍵字class
,然後輸入類別名稱,後面加左右大括號。請在左右大括號之間留出空格,以便放入類別的程式碼。
class Dice {
}
在類別定義中,您可以使用變數為類別指定一或多項屬性。真實的骰子可以有多個面、一種顏色或重量。在這項工作中,您的重點會放在骰子面數的屬性上。
- 在
Dice
類別中,新增名為sides
的var
做為骰子的面數。將sides
設為 6。
class Dice {
var sides = 6
}
大功告成了。現在您有一個很簡單的類別,代表骰子。
建立骰子類別的執行個體
有了這個 Dice
類別,等同您掌握骰子的藍圖。如要讓程式中「實際」顯示骰子,就需要建立 Dice
物件例項。(如果您需要三個骰子,就要建立三個物件執行個體。)
- 如要建立
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() {
}
}
擲出六面骰子時,系統會產生 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()
必須傳回一些內容,這個程式碼才會如預期般運作。
在先前的程式碼研究室中,您瞭解到需要為函式的輸入引數指定資料類型。同理,您需要為函式傳回的資料指定資料類型。
- 變更
roll()
函式,以指定要傳回的資料類型。在本例子中,隨機數字是Int
,因此傳回類型為Int
。指定傳回類型的語法如下:在函式名稱後面,在括號後加上冒號和空格,然後為函式的傳回類型加上Int
關鍵字。函式定義的程式碼應如下所示。
fun roll(): Int {
- 執行這個程式碼。問題檢視畫面中會顯示錯誤訊息。內容如下:
A ‘return' expression required in a function with a block body (‘{...}')
您已將函式定義變更為傳回 Int
,但系統提示您程式碼實際上並未傳回 Int
。「區塊內文」或「函式主體」是指函式的大括號之間的程式碼。您可以修正這個錯誤,做法是在函式主體結尾使用 return
陳述式傳回函式的值。
- 在
roll()
中移除println()
陳述式,並以randomNumber
的return
來取代。roll()
函式的程式碼應如下所示。
fun roll(): Int {
val randomNumber = (1..6).random()
return randomNumber
}
- 在
main()
中,移除骰子面的輸出陳述式。 - 新增陳述式,以詳盡的說明句子輸出
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
- 複製下方的現有輸出陳述式,然後貼到變更面數的後面。
- 將
myFirstDice
的輸出結果替換為對diceRoll
呼叫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
的整數。類別中的程式碼不會改變。
class Dice(val numSides: Int) {
// Code inside does not change.
}
- 在
Dice
類別中刪除sides
變數,因為您現在可以使用numSides
了。 - 此外,請將範圍修正為使用
numSides
。
您的 Dice
類別應如下所示。
class Dice (val numSides: Int) {
fun roll(): Int {
val randomNumber = (1..numSides).random()
return randomNumber
}
}
執行這個程式碼時,您會看到很多錯誤,因為您必須更新 main()
,才能處理對 Dice
類別所做的變更。
- 在
main()
中、如要建立含有 6 個面的myFirstDice
,您現在必須提供面數做為Dice
類別的引數,如下所示。
val myFirstDice = Dice(6)
- 在輸出陳述式中,將
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
的Dice
物件,這個骰子包含 20 個面。
val mySecondDice = Dice(20)
- 新增用於投擲和輸出傳回值的輸出陳述式。
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()
}
在第二個輸出陳述式中,您會將用於取得隨機數字的呼叫,放入字串範本中。您可以運用在第一個輸出陳述式中相同的做法,去除 diceRoll
變數。
- 在字串範本中呼叫
myFirstDice.roll()
並刪除diceRoll
變數。main()
程式碼的前兩行現在應如下所示。
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
類別提供另一個顏色的屬性,然後建立多個有不同面數和顏色的骰子例項! - 建立
Coin
類別,讓它能夠翻轉、建立類別執行個體,以及拋擲一些硬幣!您會如何使用random()
函式搭配範圍來完成硬幣拋擲動作呢?