建立可執隨機擲骰子的 Kotlin 程式,當使用者「擲骰子」時,系統會隨機產生結果。
學習目標
- 如何透過程式輔助方式產生隨機數字來模擬擲骰子的動作。
- 如何透過建立含有變數和方法的 Dice 類別來建構程式碼。
- 如何建立類別的物件例項、修改其變數,以及呼叫其方法。
使用隨機函式
如要擲骰子,需要設法呈現所有有效的骰子值。一般的 6 面骰子,可接受的結果有 1、2、3、4、5 和 6。IntRange 是一種資料類型,代表從起點到終點的整數範圍。
在這裡,我們使用 IntRange 定義骰子可能產生的值。
- 在
main() 函式中定義為名為diceRange的val,並給定 1 到 6 的 IntRange,代表 6 面骰子可以擲出的整數範圍。
1 | val diceRange = 1..6 |
val diceRange = 1..6等於val diceRange: IntRange = 1..6
- 使用
random()的函式,產生並傳回指定範圍的隨機數字,將結果儲存在變數中。
- 在 main() 中,定義一個為名為
randomNumber的val。 - 將
diceRange呼叫random()的結果,存在randomNumber中 。
1 | val randomNumber = diceRange.random() |
- 執行程式碼數次印出
randomNumber,可以發現隨機數字不一樣。
1 | println("隨機數字: ${randomNumber}") |
建立骰子類別
我們可以建立骰子的程式輔助藍圖,指出骰子有多個面,而且可以擲出隨機數字。此藍圖稱為類別。
接下來,就可以透過該類別建立實際的骰子物件,稱為「物件執行個體」。例如建立有 12 個面或 4 個面的骰子。
定義骰子類別
- 在下列步驟中,會定義名為
Dice的新類別來代表可投擲的骰子。
- 為了重新開始,請清除
main()函式中的程式碼。
1 | fun main() { |
- 在這個
main()函式下方,新增空白行,然後新增程式碼來建立Dice類別。如下所示,請先輸入關鍵字class,然後輸入類別名稱,後面加左右大括號。請在左右大括號之間留出空格,以便放入類別的程式碼。
1 | class Dice { |
- 在類別定義中,您可以使用變數為類別指定一或多項屬性。真實的骰子可以有多個面、一種顏色或重量。在這項工作中,您的重點會放在骰子面數的屬性上。
- 在 Dice 類別中,新增名為
sides的var做為骰子的面數。將 sides 設為 6。
1 | class Dice { |
var是可變變量,而val是不可變變量,var 在被賦予值後還能夠修改,但是val不行。
建立骰子類別的執行個體
有了這個 Dice 類別,等同您掌握骰子的藍圖。如要讓程式中「實際」顯示骰子,就需要建立 Dice 物件例項。(如果您需要三個骰子,就要建立三個物件執行個體。)
- 如要建立
Dice的物件執行個體,請在main()函式中建立名為myFirstDice的val,並將其初始化為 Dice 類別的執行個體。
- 請注意類別名稱之後的
括號,代表要從類別建立新的物件執行個體。
1 | fun main() { |
現在已經有了根據藍圖建立的 myFirstDice 物件,便可以存取其屬性。Dice 唯一的屬性是 sides。
如要存取 myFirstDice 的 sides 屬性,可以使用「點標記法」呼叫 myFirstDice.sides,讀音為「myFirstDice 點 sides」。
- 在 myFirstDice 宣告下方,新增 println() 陳述式,以輸出 myFirstDice. 的 sides 數量
1 | println(myFirstDice.sides) |
- 執行程式,應會輸出
Dice類別中定義的sides數量。
現在程式碼:
1 | fun main() { |
執行結果:
1 | 6 |
擲骰子
在 Dice類別 中新增一個用來擲骰子的 函式。
- 在類別中定義的函式也稱為「方法」。
- 在 Dice 類別 sides 變數下方﹐插入一行空行,然後建立新的函式來擲骰子。
1 | class Dice { |
- 在
roll()方法中建立一個valrandomNumber,並呼叫random()以在 1..6 範圍內產生一個隨機數字。
1 | class Dice { |
- 產生
隨機數字後將其輸出。完成的 roll() 方法應如以下程式碼所示。
1 | fun roll() { |
- 如要實際投擲
myFirstDice,請在main()中對myFirstDice呼叫roll()方法,如myFirstDice.roll(),讀音為「myFirstDice 點 roll()」。
現在程式碼:
1 | fun main() { |
執行結果:
1 | 6 |
1為隨機產生的數字
傳回擲骰子的結果值
將 roll() 方法的結果(產生的隨機數字)存在一個變數中,並回傳到 main函式。
在 main() 中,修改顯示 myFirstDice.roll() 的行。建立名為
diceRoll的val。1
val diceRoll = myFirstDice.roll()
變更
roll()函式,以指定要傳回的資料類型。在本例子中,隨機數字是Int,因此傳回類型為Int。
指定傳回類型的語法為在函式名稱後面,在括號後加上冒號和空格,然後為函式的傳回類型加上 Int 關鍵字。
1 | class Dice { |
- 在
roll()中移除println()陳述式,並以randomNumber的return來取代。
1 | class Dice { |
- 在
main函式中輸出sides和diceRoll的值。1
2
3
4
5fun main() {
val myFirstDice = Dice()
val diceRoll = myFirstDice.roll()
println("Your ${myFirstDice.sides} sided dice rolled ${diceRoll}!")
}
現在程式碼:
1 | fun main() { |
執行結果:
1 | Your 6 sided dice rolled 4! |
6為骰子的面數,4為1~6中隨機產生的數字
變更骰子面數
不是所有骰子都有 6 面!骰子有各種形狀和尺寸,有 4 面、8 面,最多可到 120 面!
- 在
Dice類別的roll()方法中,將硬式編碼的1..6改為使用sides。這樣,範圍與擲出的隨機數字便一律適用於面數。
1 | val randomNumber = (1..sides).random() |
- 在 main() 函式中,在擲出的骰子結果下方,將
myFirstDice的sides變更為 20。
1 | myFirstDice.sides = 20 |
- 複製下方的現有輸出陳述式,然後貼到變更面數的後面,然後將
myFirstDice的輸出結果替換為對diceRoll呼叫roll()方法的輸出結果。
1 | println("Your ${myFirstDice.sides} sided dice rolled ${myFirstDice.roll()}!") |
現在程式碼:
1 | fun main() { |
執行結果:
1 | Your 6 sided dice rolled 2! |
- 第一行為
6面骰子的訊息,第二行為20面骰子的訊息
自訂骰子
修改 Dice類別,以便在建立新的執行個體時指定面數。
- 變更
Dice類別定義就能提供面數,這與函式接受輸入引數的方式類似。
- 修改
Dice類別定義,以接受名為numSides的整數。類別中的程式碼不會改變。1
2
3class Dice(val numSides: Int) {
// Code inside does not change.
} - 在
Dice類別中刪除sides變數,並將用到sides的地方修改成numSides。
1 | class Dice(val numSides: Int) { |
- 在
main()中,如要建立含有 6 個面的myFirstDice,您現在必須提供面數做為 Dice 類別的引數。
1 | val myFirstDice = Dice(6) |
- 在輸出陳述式中,將 sides 變更為 numSides。
- 然後在下方,刪除將 sides 變更為 20 的程式碼,因為該變數已不再存在。
- 請一併刪除下方的 println 陳述式。
main() 函式應如下列程式碼所示:
1 | fun main() { |
- 新增程式碼來建立並輸出第二個名為
mySecondDice的Dice物件,這個骰子包含20個面。
1 | val mySecondDice = Dice(20) |
- 新增用於投擲和輸出傳回值的輸出陳述式。
1 | println("Your ${mySecondDice.numSides} sided dice rolled ${mySecondDice.roll()}!") |
現在程式碼:
1 | fun main() { |
執行結果:
1 | Your 6 sided dice rolled 3! |
- 第一行為
第一個骰子(6面骰子)的執行結果,第二行為第二個骰子(20面骰子)的執行結果。
採用完善程式設計做法
編寫程式碼時,應保持簡潔。
- 可以去除
randomNumber變數,並直接傳回隨機數字。
1 | fun roll(): Int { |
- 在字串範本中呼叫
myFirstDice.roll()並刪除diceRoll變數。
1 | println("Your ${myFirstDice.numSides} sided dice rolled ${myFirstDice.roll()} |
最終的程式碼:
1 | fun main() { |
總結
- 對
IntRange呼叫random()函式以產生隨機數字:(1..6).random() 類別就像是物件的藍圖,會包含屬性和行為,可做為變數和函式來實作。類別的執行個體代表一個物件,通常是實物,例如骰子。您可以對物件呼叫動作,並變更其屬性。- 可以在建立執行個體時,為類別提供值。例如:
class Dice(val numSides: Int),然後使用Dice(6)建立執行個體。 - 函式可以傳回結果。在函式定義中指定要傳回的資料類型,並在函式主體中使用
return來傳回一些內容。例如:fun example(): Int { return 5 }
自行練習
建立 Coin 類別,讓它能夠翻轉、建立類別執行個體,以及拋擲一些硬幣!您會如何使用 random() 函式搭配範圍來完成硬幣拋擲動作呢?
程式碼:
1 | fun main() { |
執行結果:
1 | Your 2 sided coin rolled 1! |
- 硬幣有正反2面,假設
1為正面,2為反面。
建立互動式的Dice Roller應用程式
建立一個 Dice Roller Android 應用程式,讓使用者能在應用程式中按一下
Button來擲骰子。擲骰子結果會顯示在螢幕上的TextView中。
學習目標
- 如何將
Button新增至 Android 應用程式。 - 如何新增輕觸應用程式
Button時的行為。 - 如何開啟及修改應用程式的
Activity程式碼。 - 如何顯示
Toast訊息。 - 如何在應用程式執行期間更新
TextView的內容。
設定應用程式
*範例為使用 Empty View Activity 構建App,非新版 Empty Activity
- 在 Create New Project(建立新專案) 中,使用
Empty View Activity範本建立新的 Kotlin 專案。
- 將應用程式命名為「Dice Roller」,指定最低 API 級別 19 (KitKat)。
建立應用程式的版面配置
- 開啟
activity_main.xml,在「Hello World」TextView 下方新增Button。
- TextView 和 Button 皆位於 ViewGroup 類型的
ConstraintLayout中。
- 由於
Button位於ConstraintLayout內,因此您必須設定垂直和水平限制條件進行定位。
安排按鈕的位置
新增從 Button 頂端到 TextView 底部的垂直限制條件。
- 在「Design」檢視畫面中,按住
Button上方邊緣有藍色邊框的白色圓圈。拖曳指標,並箭頭會遵循指標。當您移動到「Hello World」TextView底部邊緣時放開。這項操作會建立版面配置限制條件,且Button往上滑至TextView正下方。
- 查看「Layout Editor」(版面配置編輯器) 右側的「Attributes」(屬性)。請留意「Constraint Widget」中,新版面配置的限制條件設定為
TextView的底部,例如Top → BottomOf textView (0dp)。
- (0dp) 表示邊界為 0。已發生缺少水平限制條件的錯誤。
- 新增從
Button的左側至父項ConstraintLayout的左側的水平限制條件。並於右側重複操作,將Button的右邊緣連接到ConstraintLayout的右邊緣。結果看起來會像這樣
- 在仍然選取
Button的情況下,「Constraint Widget」應如下所示。您新增了兩個額外的限制條件:Start → StartOf parent (0dp)和End → EndOf parent (0dp)。這表示Button是水平置中於其父項ConstraintLayout中。
- 執行應用程式,應如以下螢幕截圖所示。
變更按鈕文字
- 在版面配置編輯器中,如果已選取 Button,請前往「Attributes」,將「text」變更為「Roll」,然後按下 Enter 鍵 (Mac 則是按下 Return 鍵)。
- 在「Component Tree」中,Button 旁邊會顯示橘色的警示三角形。只要游標懸停在三角形上,就會顯示訊息。Android Studio 在您的應用程式的程式碼中偵測到硬式編碼字串 (「Roll」),因此建議您改用字串資源。
- 在「Component Tree」中點選橘色三角形。在訊息底部的「Suggested Fix」下方,按一下「Fix」按鈕。(您可能需要向下捲動頁面。)
- 系統隨即會開啟「Extract Resource」(擷取資源) 對話方塊。擷取字串代表擷取「Roll」文字,並在 strings.xml 中建立一個名為 roll 的字串資源 (app > res > values > strings.xml)。由於預設值正確無誤,因此請按一下「OK」。
- 在「Attributes」中,Button 的 text 屬性現在會指向 @string/roll
設定 TextView 的樣式
小型「Hello, World!」訊息取代為數字以顯示搖骰子值,並放大字型,讓使用者一目瞭然。
- 在「Design Editor」中選取
TextView,接著將TextView的textSize變更為 36sp,以便放大且易讀。
- 清除
TextView的text屬性。您不用在 TextView 中顯示任何資訊,直到使用者擲骰子為止。
不過,當您編輯應用程式的版面配置和程式碼時,在 TextView 中查看部分文字會很有幫助。為此,您可以在 TextView 新增文字,這些文字只會顯示在版面配置預覽中,但在應用程式執行時不會顯示。
在「Common Attributes」下方找到「text」屬性,下方則顯示含有工具圖示的「text」屬性。text 屬性是在應用程式執行時顯示給使用者。含有工具圖示的「text」屬性是專為您 (開發人員) 設計的「tools text」屬性。
在
TextView中將工具文字設定為「1」(假設您有一個顯示「1」的骰子)。「1」只會出現在 Android Studio 的「Design Editor」(設計編輯器) 中,但當您在實際裝置或模擬器上執行應用程式時,畫面上不會顯示「1」。
啟用自動匯入作業
如果您同時使用更多類別,新增 import 陳述式會變得困難。幸好,使用他人提供的類別時,Android Studio 可協助您選擇正確的匯入作業。
在 macOS 中:
-> 依序前往「File」(檔案) >「New Project Settings」(新專案設定) >「Preferences for New Project…」(新專案的偏好設定…) 開啟設定。
-> 依序展開「Other Settings」(其他設定) >「Auto Import」(自動匯入)。
-> 在「Java」和「Kotlin」區段中,確定已勾選「Add unambiguous imports on the fly」(快速新增不明確的匯入) 和「Optimize imports on the fly (for current project)」(快速最佳化匯入 (適用於目前的專案))。
-> 按下「OK」儲存變更,然後關閉設定。
提高按鈕互動性
在點選按鈕時顯示訊息
在按下按鈕時,畫面底部顯示簡短訊息。
setContentView()呼叫後,將下列程式碼新增至onCreate()方法。findViewById()方法會在版面配置中找到Button。R.id.button是Button的資源 ID,這是其專屬識別碼。
1 | val rollButton: Button = findViewById(R.id.button) |
- 使用
rollButton物件並呼叫setOnClickListener()方法,在物件上設定點擊事件監聽器,來監聽使用者是否點選Button。
- 在方法名稱後面使用大括號,而不要使用括號。這是一個宣告
Lambda的特殊語法,詳情請見未來程式碼研究室的內容。
1 | rollButton.setOnClickListener { |
- 呼叫
Toast.makeText()即可建立內含文字 “Dice Rolled!” 的Toast。
1 | val toast = Toast.makeText(this, "Dice Rolled!", Toast.LENGTH_SHORT) |
- 然後呼叫
show()方法,讓Toast自行顯示。
1 | val toast = Toast.makeText(this, "Dice Rolled!", Toast.LENGTH_SHORT).show() |
- 執行應用程式,然後按一下「Roll」按鈕。浮動式訊息應會以彈出式視窗顯示在螢幕底部,而且不久後就會消失。
在點選按鈕時更新 TextView
如果不想顯示臨時 Toast 訊息,您必須撰寫程式碼,並在點選「Roll」按鈕時更新畫面上的 TextView。
- 註解
Toast的程式碼行。 - 建立一個名稱為
resultTextView的新變數,以便儲存TextView。
1 | val resultTextView: TextView = findViewById(R.id.textView) |
- 將
resultTextView上的文字設定為在雙引號內的6。
1 | resultTextView.text = "6" |
新增擲骰子邏輯
新增 Dice (骰子) 類別
在 MainActivity 類別中加上最後一個大括號後,透過
roll()方法建立Dice類別。1
2
3
4
5
6class Dice(val numSides: Int) {
fun roll(): Int {
return (1..numSides).random()
}
}將游標懸停在
numSides上,畫面上就會顯示「Property ‘numSides' could be private」彈出式視窗。
- 將
numSides標示為private後,您只能在Dice類別中存取。由於只有使用numSides的程式碼才會位於Dice類別內,因此可為Dice類別指定這個引數private。
- 按一下「Make ‘numSides’ ‘private」,繼續透過 Android Studio 修正建議問題。
建立 rollDice() 方法
現在您已經新增 Dice 類別至應用程式,必須更新 MainActivity 才能使用該類別。如要妥善整理程式碼,請將所有擲骰子邏輯放入一個函式中。
- 將點擊事件監聽器中將文字設定為
"6"的程式碼替換為呼叫rollDice()。
1 | rollButton.setOnClickListener { |
- 因為尚未定義
rollDice(),Android Studio 會標記誤並以紅色顯示rollDice()。滑鼠游標懸停在rollDice()上時,Android Studio 會顯示問題和可能的解決方法。
- 按一下「More actions…」開啟選單。(Mac 可按下
Option+Enter開啟選單。) - 選取
「Create function ‘rollDice'」。Android Studio 會為 MainActivity 中的函式建立空白定義。
1 | private fun rollDice() { |
建立新的 Dice 物件例項
在這個步驟中,您必須建立 rollDice() 方法並擲骰子,然後在 TextView 中顯示結果。
- 在
rollDice()中刪除 TODO() 呼叫,並加入程式碼即可建立具有 6 個面的骰子。
1 | val dice = Dice(6) |
- 呼叫
roll()方法即可擲骰子,並將結果儲存在名為diceRoll的變數中。
1 | val diceRoll = dice.roll() |
- 呼叫
findViewById()即可找到TextView。
1 | val resultTextView: TextView = findViewById(R.id.textView) |
- 將
diceRoll轉換成字串,並使用該字串更新resultTextView的文字。
- 變數
diceRoll是數字,但TextView使用文字。您可以在diceRoll上使用toString()方法,將數字轉換為字串。
1 | resultTextView.text = diceRoll.toString() |
- 執行應用程式,點選按鈕即可擲骰子。
採用完善程式設計做法
團隊合作時,理想的做法是以類似的方式撰寫程式碼,讓程式碼之間保持一致。因此,Android 提供樣式指南來說明如何編寫 Android 程式碼,包括命名慣例、格式和其他遵循的建議做法。
撰寫 Android 程式碼時遵循這些指南:Android 開發人員適用的 Kotlin 樣式指南。
清理您的程式碼
- 縮減程式碼
將程式碼縮減成較短行的程式碼,讓程式碼更精簡。以下範例是設定Button點擊事件監聽器的程式碼。
1 | rollButton.setOnClickListener { |
由於點擊事件監聽器的操作說明只有 1 行,因此您可以縮減 rollDice() 方法呼叫,將此呼叫和大括號全部置於一行。
1 | rollButton.setOnClickListener { rollDice() } |
- 重新設定程式碼格式
現在,您必須重新設定程式碼格式,確保程式碼符合 Android 建議的程式碼格式規範。
在 MainActivity.kt 類別中,用 Windows 的鍵盤快速鍵 Control+A (Mac 則是 Command+A) 就可選取檔案中的所有文字。或者您可以在 Android Studio 的選單中依序點選「Edit」>「Select All」。
選取檔案中的所有文字後,在 Android Studio 的選單中依序點選「Code」>「Reformat Code」,或使用鍵盤快速鍵 Ctrl+Alt+L (Mac 則是 Command+Option+L)。
- 這會更新程式碼的格式,包括空白字元、縮排等等。您可能還看不到任何變化,這很好。您的程式碼已正確格式化!
- 為程式碼加上註解
為每個類別 (MainActivity和Dice是您的應用程式中僅有的類別) 和您撰寫的每個方法新增註解。請在註解的開頭和結尾使用/**和*/符號,告訴系統這不是程式碼。系統在執行程式碼時會忽略這些行。
自行練習
在應用程式中新增另一個骰子。按一下「Roll」按鈕應擲 2 個骰子。螢幕上應在 2 個不同的 TextViews 中顯示結果。
執行畫面:
程式碼:
1 | /** |