במהלך ה-codelabs האלה, תיצרו אפליקציית קובייה מתגלגלת ל-Android. כשמשתמשים "מגלגלים את הקובייה", נוצרת תוצאה אקראית. התוצאה מתחשבת במספר הצדדים של הקובייה. לדוגמה, אפשר להטיל קובייה בעלת 6 צדדים רק עם ערכים מ-1 עד 6.
כך תיראה האפליקציה הסופית.
כדי לעזור לכם להתמקד במושגי התכנות החדשים של האפליקציה הזו, תשתמשו בכלי לתכנות ב-Kotlin שמבוסס על דפדפן כדי ליצור פונקציונליות ליבה של האפליקציה. התוכנית תציג את התוצאות במסוף. בהמשך תטמיעו את ממשק המשתמש ב-Android Studio.
ב-codelab הראשון הזה, תיצרו תוכנית Kotlin שמדמה הטלת קובייה ומציגה מספר אקראי, בדיוק כמו קובייה.
דרישות מוקדמות
- איך פותחים, עורכים ומריצים קוד בכתובת https://try.kotlinlang.org/
- ליצור ולהריץ תוכנית Kotlin שמשתמשת במשתנים ובפונקציות, ומדפיסה תוצאה במסוף.
- עיצוב מספרים בתוך טקסט באמצעות תבנית מחרוזת עם הסימון
${variable}
.
מה תלמדו
- איך ליצור מספרים אקראיים באופן אוטומטי כדי לדמות הטלות קוביות.
- איך יוצרים מבנה לקוד באמצעות יצירת מחלקה
Dice
עם משתנה ושיטה. - איך יוצרים מופע אובייקט של מחלקה, משנים את המשתנים שלו ומפעילים את השיטות שלו.
מה תפַתחו
- תוכנית Kotlin בכלי לתכנות Kotlin מבוסס-דפדפן שיכול לבצע הטלת קובייה אקראית.
מה צריך
- מחשב עם חיבור לאינטרנט
לעתים קרובות יש במשחקים אלמנט אקראי. יכול להיות שתזכו בפרס אקראי או שתתקדמו במספר אקראי של צעדים בלוח המשחק. בחיים היום-יומיים, אתם יכולים להשתמש במספרים ובאותיות אקראיים כדי ליצור סיסמאות בטוחות יותר.
במקום לגלגל קוביות אמיתיות, אפשר לכתוב תוכנית שמדמה גלגול קוביות. בכל הטלת קובייה, התוצאה יכולה להיות כל מספר בטווח הערכים האפשריים. למזלכם, אתם לא צריכים לבנות גנרטור משלכם של מספרים אקראיים בשביל תוכנית כזו. לרוב שפות התכנות, כולל Kotlin, יש דרך מובנית ליצירת מספרים אקראיים. במשימה הזו, תשתמשו בקוד Kotlin כדי ליצור מספר אקראי.
הגדרת קוד התחלתי
- בדפדפן, פותחים את האתר https://try.kotlinlang.org/.
- מוחקים את כל הקוד הקיים בעורך הקוד ומחליפים אותו בקוד שבהמשך. זו הפונקציה
main()
שעבדתם איתה ב-codelabs קודמים (ראו את ה-codelab Write your first Kotlin program).
fun main() {
}
שימוש בפונקציה random
כדי לגלגל קובייה, צריך דרך לייצג את כל הערכים האפשריים של גלגול הקובייה. בקוביות רגילות עם 6 צדדים, התוצאות המקובלות הן: 1, 2, 3, 4, 5 ו-6.
קודם למדנו שיש סוגים של נתונים כמו Int
למספרים שלמים ו-String
לטקסט. IntRange
הוא סוג נתונים נוסף, והוא מייצג טווח של מספרים שלמים מנקודת התחלה ועד נקודת סיום. IntRange
הוא סוג נתונים מתאים לייצוג הערכים האפשריים שמתקבלים בהטלת קובייה.
- בתוך הפונקציה
main()
, מגדירים משתנה כ-val
בשםdiceRange
. מקצים לו ערך שלIntRange
מ-1 עד 6, שמייצג את טווח המספרים השלמים שאפשר לקבל בהטלת קובייה בעלת 6 צדדים.
val diceRange = 1..6
אפשר לדעת ש-1..6
הוא טווח ב-Kotlin כי הוא כולל מספר התחלה, שתי נקודות ואז מספר סיום (ללא רווחים ביניהם). דוגמאות נוספות לטווחים של מספרים שלמים הן 2..5
למספרים 2 עד 5, ו-100..200
למספרים 100 עד 200.
בדומה לקריאה לפונקציה println()
שמורה למערכת להדפיס את הטקסט שמועבר לה, אפשר להשתמש בפונקציה שנקראת random()
כדי ליצור ולהחזיר מספר אקראי בטווח נתון. כמו קודם, אפשר לאחסן את התוצאה במשתנה.
- בתוך
main()
, מגדירים משתנה כ-val
שנקראrandomNumber
. - הגדרת הערך של
randomNumber
כתוצאה של קריאה לפונקציהrandom()
בטווחdiceRange
, כמו בדוגמה שלמטה.
val randomNumber = diceRange.random()
שימו לב שקוראים לפונקציה random()
ב-diceRange
באמצעות נקודה בין המשתנה לקריאה לפונקציה. אפשר לקרוא את זה כ'יצירת מספר אקראי מתוך diceRange
'. התוצאה מאוחסנת במשתנה randomNumber
.
- כדי לראות את המספר שנוצר באופן אקראי, משתמשים בסימון של פורמט מחרוזת (שנקרא גם 'תבנית מחרוזת')
${randomNumber}
כדי להדפיס אותו, כמו בדוגמה שלמטה.
println("Random number: ${randomNumber}")
הקוד הסופי אמור להיראות כך.
fun main() {
val diceRange = 1..6
val randomNumber = diceRange.random()
println("Random number: ${randomNumber}")
}
- מריצים את הקוד כמה פעמים. בכל פעם, הפלט אמור להיראות כמו בדוגמה שלמטה, עם מספרים אקראיים שונים.
Random number: 4
כשמטילים קוביות, הן אובייקטים אמיתיים בידיים. הקוד שכתבתם עכשיו עובד מצוין, אבל קשה לדמיין שהוא קשור לקוביות אמיתיות. כשמארגנים תוכנית כך שהיא תהיה דומה יותר לדברים שהיא מייצגת, קל יותר להבין אותה. לכן, יהיה נחמד אם יהיו קוביות פרוגרמטיות שאפשר לגלגל!
כל הקוביות פועלות באופן זהה. יש להם את אותן התכונות, כמו מספר הצדדים, והם מתנהגים באותו אופן, למשל אפשר לגלגל אותם. ב-Kotlin, אפשר ליצור תוכנית של קובייה שקובעת שלקובייה יש צדדים ושהיא יכולה להגריל מספר אקראי. התוכנית הזו נקראת class (מחלקה).
מתוך המחלקה הזו, אפשר ליצור אובייקטים של קוביות, שנקראים מופעים של אובייקטים. לדוגמה, אפשר ליצור קובייה עם 12 צדדים או קובייה עם 4 צדדים.
הגדרת סיווג של קוביות
בשלבים הבאים תגדירו מחלקה חדשה בשם Dice
שתייצג קובייה שאפשר לגלגל.
- כדי להתחיל מחדש, מוחקים את הקוד בפונקציה
main()
כך שהקוד שמתקבל יהיה כמו הקוד שמוצג למטה.
fun main() {
}
- מתחת לפונקציה
main()
, מוסיפים שורה ריקה ואז מוסיפים קוד ליצירת המחלקהDice
. כמו שמוצג למטה, מתחילים במילת המפתחclass
, ואחריה שם הכיתה, ואז סוגריים מסולסלים פותחים וסוגרים. משאירים רווח בין הסוגריים המסולסלים כדי להזין את הקוד של הכיתה.
class Dice {
}
בתוך הגדרת הכיתה, אפשר לציין מאפיין אחד או יותר לכיתה באמצעות משתנים. לקוביות אמיתיות יכולים להיות מספר צדדים, צבע או משקל. במשימה הזו, נתמקד במאפיין של מספר הצדדים של הקובייה.
- בתוך המחלקה
Dice
מוסיפיםvar
בשםsides
למספר הצדדים של הקובייה. מגדירים אתsides
ל-6.
class Dice {
var sides = 6
}
זה הכול. עכשיו יש לכם מחלקה פשוטה מאוד שמייצגת קובייה.
יצירת מופע של המחלקה Dice
עם המחלקה Dice
הזו, יש לכם תוכנית של קובייה. כדי שתהיה לכם קובייה בפועל בתוכנית, אתם צריכים ליצור מופע של אובייקט Dice
. (ואם הייתם צריכים שלוש קוביות, הייתם יוצרים שלוש מופעים של אובייקט).
- כדי ליצור מופע אובייקט של
Dice
, בפונקציהmain()
, יוצריםval
בשםmyFirstDice
ומאתחלים אותו כמופע של המחלקהDice
. שימו לב לסוגריים אחרי שם המחלקה, שמציינים שאתם יוצרים מופע אובייקט חדש מהמחלקה.
fun main() {
val myFirstDice = Dice()
}
עכשיו, אחרי שיש לכם אובייקט myFirstDice
, כלומר דבר שנוצר מהתוכנית, אתם יכולים לגשת למאפיינים שלו. המאפיין היחיד של Dice
הוא sides
. כדי לגשת לנכס, משתמשים בסימון הנקודות. לכן, כדי לגשת למאפיין sides
של myFirstDice
, קוראים ל-myFirstDice.sides
, שמבוטא 'myFirstDice
נקודה sides
'.
- מתחת להצהרה של
myFirstDice
, מוסיפים הצהרה שלprintln()
כדי להציג את מספרsides
שלmyFirstDice.
println(myFirstDice.sides)
הקוד אמור להיראות כך.
fun main() {
val myFirstDice = Dice()
println(myFirstDice.sides)
}
class Dice {
var sides = 6
}
- מריצים את התוכנית, והיא אמורה להפיק את מספר
sides
שמוגדר במחלקהDice
.
6
עכשיו יש לכם כיתה Dice
וקוביות אמיתיות myFirstDice
עם 6 sides
.
בואו נטיל את הקוביות!
הטלת קוביות
השתמשתם בעבר בפונקציה כדי לבצע את הפעולה של הדפסת שכבות של עוגה. גם הטלת קובייה היא פעולה שאפשר להטמיע כפונקציה. מכיוון שאפשר להטיל את כל הקוביות, אפשר להוסיף פונקציה להטלת קובייה בתוך המחלקה Dice
. פונקציה שמוגדרת בתוך מחלקה נקראת גם שיטה.
- בכיתה
Dice
, מתחת למשתנהsides
, מוסיפים שורה ריקה ואז יוצרים פונקציה חדשה להטלת הקובייה. מתחילים במילת המפתחfun
של Kotlin, ואחריה שם השיטה, ואחריה סוגריים()
, ואחריהם סוגריים מסולסלים פותחים וסוגרים{}
. אפשר להשאיר שורה ריקה בין הסוגריים המסולסלים כדי לפנות מקום לקוד נוסף, כמו בדוגמה הבאה. הכיתה אמורה להיראות כך.
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()
, קוראים ל-methodroll()
ב-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)
}
}
- מריצים את הקוד. תוצאת הטלת הקובייה האקראית תופיע מתחת למספר הצדדים. מריצים את הקוד כמה פעמים ורואים שמספר הצדדים נשאר זהה, אבל הערך של הטלת הקובייה משתנה.
6 4
מעולה! הגדרתם מחלקה Dice
עם משתנה sides
ופונקציה roll()
. בפונקציה main()
יצרתם מופע חדש של אובייקט Dice
ואז קראתם למתודה roll()
כדי ליצור מספר אקראי.
בשלב הזה אתם מדפיסים את הערך של randomNumber
בפונקציה roll()
וזה עובד מצוין. אבל לפעמים יותר שימושי להחזיר את התוצאה של פונקציה לכל מה שקרא לפונקציה. לדוגמה, אפשר להקצות את התוצאה של השיטה roll()
למשתנה, ואז להזיז שחקן בסכום הזה. איך עושים את זה?
- בקטע
main()
משנים את השורהmyFirstDice.roll()
. יוצריםval
בשםdiceRoll
. מגדירים אותו כערך שמוחזר על ידי השיטהroll()
.
val diceRoll = myFirstDice.roll()
הפעולה הזו לא עושה כלום כי roll()
לא מחזירה כלום. כדי שהקוד הזה יפעל כמצופה, הפונקציה roll()
צריכה להחזיר ערך כלשהו.
בסדנאות קוד קודמות למדתם שצריך לציין סוג נתונים לארגומנטים של פונקציות. באותו אופן, צריך לציין סוג נתונים לנתונים שפונקציה מחזירה.
- משנים את הפונקציה
roll()
כדי לציין איזה סוג נתונים יוחזר. במקרה הזה, המספר האקראי הואInt
, ולכן סוג ההחזרה הואInt
. התחביר לציון סוג ההחזרה הוא: אחרי שם הפונקציה, אחרי הסוגריים, מוסיפים נקודתיים, רווח ואז את מילת המפתחInt
של סוג ההחזרה של הפונקציה. הגדרת הפונקציה צריכה להיראות כמו הקוד שבהמשך.
fun roll(): Int {
- מריצים את הקוד הזה. תופיע שגיאה בתצוגת הבעיות. ההודעה:
A ‘return' expression is required in a function with a block body.
שינית את הגדרת הפונקציה כך שתחזיר Int
, אבל המערכת מתלוננת שהפונקציה
הקוד לא מחזיר בפועל Int
. 'גוף הבלוק' או 'גוף הפונקציה' מתייחסים לקוד שבין הסוגריים המסולסלים של פונקציה. כדי לתקן את השגיאה הזו, צריך להחזיר ערך מפונקציה באמצעות הצהרת return
בסוף גוף הפונקציה.
- ב-
roll()
, מסירים את ההצהרהprintln()
ומחליפים אותה בהצהרהreturn
לגביrandomNumber
. הפונקציה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
}
}
לא לכל הקוביות יש 6 צדדים! יש קוביות מכל הצורות והגדלים: 4 צדדים, 8 צדדים, עד 120 צדדים!
- בשיטה
roll()
של המחלקהDice
, משנים את הערך הקבוע1..6
לשימוש ב-sides
, כך שהטווח, ומכאן המספר האקראי שיוגרל, תמיד יהיה נכון למספר הצדדים.
val randomNumber = (1..sides).random()
- בפונקציה
main()
, מתחת להדפסה של הטלת הקובייה ואחריה, משנים אתsides
שלFirstDice
ל-20.
myFirstDice.sides = 20
- מעתיקים את הצהרת ההדפסה הקיימת שבהמשך ומדביקים אותה אחרי המקום שבו שיניתם את מספר הצדדים.
- מחליפים את ההדפסה של
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
}
}
- מריצים את התוכנית וצריכה להופיע הודעה לגבי הקובייה בעלת 6 הצדדים, והודעה שנייה לגבי הקובייה בעלת 20 הצדדים.
Your 6 sided dice rolled 3! Your 20 sided dice rolled 15!
הרעיון של מחלקה הוא לייצג דבר כלשהו, לרוב משהו פיזי בעולם האמיתי. במקרה הזה, המחלקה Dice
מייצגת קובייה פיזית. בעולם האמיתי, אי אפשר לשנות את מספר הצדדים של קובייה. אם רוצים מספר אחר של צדדים, צריך להשיג קובייה אחרת. מבחינה תכנותית, במקום לשנות את מאפיין הצדדים של מופע קיים של אובייקט Dice
, צריך ליצור מופע חדש של אובייקט קובייה עם מספר הצדדים שרוצים.
במשימה הזו תשנו את המחלקה Dice
כדי שתוכלו לציין את מספר הצדדים כשתיצרו מופע חדש. משנים את הגדרת המחלקה Dice
כך שתקבל ארגומנט למספר הצדדים. זה דומה לאופן שבו פונקציה יכולה לקבל ארגומנטים כקלט.
- משנים את הגדרת המחלקה
Dice
כך שתקבל ארגומנט מסוג integer בשם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()
, כדי ליצורmyFirstDice
עם 6 צדדים, צריך להעביר את מספר הצדדים כארגומנט למחלקה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}!")
}
- אחרי שמדפיסים את הטלת הקובייה הראשונה, מוסיפים קוד כדי ליצור ולהדפיס אובייקט שני של
Dice
בשםmySecondDice
עם 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!
כשכותבים קוד, עדיף לכתוב אותו בצורה תמציתית. אפשר להיפטר מהמשתנה 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()
}
}
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() עם טווח כדי להטיל מטבע?