1. 시작하기 전에
이 과정의 Codelab에서는 Dice Roller Android 앱을 빌드합니다. 이 앱에서는 사용자가 '주사위를 굴리면' 결과가 임의로 생성됩니다. 결과는 주사위의 면 수를 고려합니다. 예를 들어 6면 주사위로는 1~6의 값만 굴릴 수 있습니다.
최종 앱은 다음과 같이 표시됩니다.
이 앱의 새 프로그래밍 개념에 집중할 수 있도록 브라우저 기반 Kotlin 프로그래밍 도구를 사용하여 핵심 앱 기능을 만듭니다. 프로그램은 결과를 콘솔에 출력합니다. 나중에 Android 스튜디오에서 사용자 인터페이스를 구현합니다.
이 첫 번째 Codelab에서는 실제 주사위처럼 주사위 굴리기를 시뮬레이션하여 랜덤 숫자를 출력하는 Kotlin 프로그램을 만듭니다.
기본 요건
- https://developer.android.com/training/kotlinplayground에서 코드를 열어 수정하고 실행하는 방법을 이해하고 있습니다.
- 변수와 함수를 사용하고 결과를 콘솔에 출력하는 Kotlin 프로그램을 만들고 실행할 수 있습니다.
${variable}
표기법으로 문자열 템플릿을 사용하여 텍스트 내의 숫자 형식을 지정할 수 있습니다.
학습할 내용
- 프로그래매틱 방식으로 랜덤 숫자를 생성하여 주사위 굴리기를 시뮬레이션하는 방법
- 변수와 메서드로
Dice
클래스를 만들어 코드를 구조화하는 방법 - 클래스의 객체 인스턴스를 만들고 변수를 수정하며 메서드를 호출하는 방법
빌드할 항목
- 브라우저 기반 Kotlin 프로그래밍 도구에서 랜덤 주사위 굴리기를 실행할 수 있는 Kotlin 프로그램
필요한 항목
- 인터넷이 연결된 컴퓨터
2. 랜덤 숫자 굴리기
게임에는 임의의 요소가 있는 경우가 많습니다. 임의의 상품을 받거나 게임 보드에서 임의 개수의 단계를 전진할 수 있습니다. 일상에서는 임의의 숫자와 문자를 사용하여 더 안전한 비밀번호를 생성할 수 있습니다.
실제 주사위를 굴리는 대신 주사위 굴리기를 시뮬레이션하는 프로그램을 작성할 수 있습니다. 주사위를 굴릴 때마다 가능한 값 범위 내의 임의 숫자가 결과로 나올 수 있습니다. 다행히 이러한 프로그램을 위해 자체 랜덤 숫자 생성기를 빌드하지 않아도 됩니다. Kotlin을 비롯한 대다수 프로그래밍 언어는 랜덤 숫자를 생성하는 방식을 기본적으로 갖추고 있습니다. 여기서는 Kotlin 코드를 사용하여 랜덤 숫자를 생성합니다.
시작 코드 설정
- 브라우저에서 https://developer.android.com/training/kotlinplayground 웹사이트를 엽니다.
- 코드 편집기에서 기존 코드를 모두 삭제하고 아래의 코드로 바꿉니다. 이전 Codelab에서 작업한
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. Dice 클래스 만들기
주사위를 굴릴 때 주사위는 손 안에 있는 실제 물건입니다. 그러나 방금 작성한 코드가 완벽하게 작동하더라도 실제 주사위라고 생각하기는 어렵습니다. 구현해 내는 것과 더 비슷하도록 프로그램을 구성하면 이를 이해하기가 더 쉬워집니다. 따라서 굴릴 수 있는 주사위를 프로그래매틱 방식으로 만드는 것이 좋습니다.
모든 주사위는 기본적으로 동일하게 작동합니다. 속성(예: 면)이 동일하고 동작(예: 굴릴 수 있음)도 동일합니다. Kotlin에서는 주사위에 면이 있고 랜덤 숫자를 굴릴 수 있다고 표시하는 프로그래매틱 방식의 주사위 청사진을 만들 수 있습니다. 이 청사진을 클래스라고 합니다.
이 클래스에서 객체 인스턴스라는 실제 주사위 객체를 만들 수 있습니다. 예를 들어 12면 주사위나 4면 주사위를 만들 수 있습니다.
Dice 클래스 정의
다음 단계에서는 Dice
라는 새 클래스를 정의하여 굴릴 수 있는 주사위를 나타냅니다.
- 새로 시작하려면
main()
함수에서 코드를 지워서 아래와 같은 코드를 만듭니다.
fun main() {
}
- 이
main()
함수 아래에 빈 줄을 추가하고 코드를 추가하여Dice
클래스를 만듭니다. 아래와 같이 키워드class
, 클래스 이름, 여는 중괄호, 닫는 중괄호 순으로 작성합니다. 중괄호 사이에 공백을 두어 클래스의 코드를 넣습니다.
class Dice {
}
클래스 정의 내에서 변수를 사용하여 클래스의 속성을 하나 이상 지정할 수 있습니다. 실제 주사위는 면과 색상, 무게가 다양할 수 있습니다. 이 작업에서는 주사위의 면 수에 관한 속성에 중점을 둡니다.
Dice
클래스 내에서 주사위 면 수를 위한sides
라는var
를 추가합니다.sides
를 6으로 설정합니다.
class Dice {
var sides = 6
}
이상입니다. 이제 주사위를 나타내는 매우 간단한 클래스가 생겼습니다.
Dice 클래스의 인스턴스 만들기
이 Dice
클래스로 주사위 개념에 관한 청사진을 보유하게 됩니다. 프로그램에 실제 주사위를 포함하려면 Dice
객체 인스턴스를 만들어야 합니다. 주사위가 3개 있어야 하면 객체 인스턴스를 3개 만듭니다.
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
클래스와 sides
가 6인 실제 주사위 myFirstDice
가 있습니다.
주사위를 굴려봅시다.
주사위 굴리기
이전에는 함수를 사용하여 케이크 층을 출력하는 작업을 실행했습니다. 주사위 굴리기도 함수로 구현할 수 있는 작업입니다. 모든 주사위를 굴릴 수 있으므로 Dice
클래스 내에서 함수를 추가하면 됩니다. 클래스 내에서 정의된 함수를 메서드라고도 합니다.
Dice
클래스의sides
변수 아래에서 빈 줄을 삽입하고 주사위를 굴리는 새 함수를 만듭니다. Kotlin 키워드fun
, 메서드 이름, 괄호()
, 여는 중괄호, 닫는 중괄호{}
순으로 작성합니다. 아래와 같이 중괄호 사이에 빈 줄을 두어 코드를 추가할 공간을 만듭니다. 클래스는 다음과 같이 표시됩니다.
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()
에서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()
에서 무언가를 반환해야 합니다.
이전 Codelab에서는 함수의 입력 인수를 위한 데이터 유형을 지정해야 한다는 것을 알아봤습니다. 같은 방식으로 함수가 반환하는 데이터의 데이터 유형을 지정해야 합니다.
roll()
함수를 변경하여 반환할 데이터 유형을 지정합니다. 이 경우 랜덤 숫자는Int
이므로 반환 유형은Int
입니다. 반환 유형을 지정하는 구문은 다음과 같습니다. 함수 이름, 괄호, 콜론, 공백, 함수 반환 유형의Int
키워드 순으로 작성합니다. 함수 정의는 아래 코드와 같이 표시됩니다.
fun roll(): Int {
- 코드를 실행합니다. Problems View에 오류가 표시됩니다. 오류는 다음과 같습니다.
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()
에서 주사위 면을 위한 print 문을 삭제합니다.- 정보를 제공하는 문장에
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
- 면 수를 변경한 다음 아래에 기존 출력 문을 복사하여 붙여넣습니다.
diceRoll
의 출력을myFirstDice
의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
객체 인스턴스의 sides 속성을 변경하는 대신 원하는 면 수로 새 주사위 객체 인스턴스를 만들어야 한다는 의미입니다.
이 작업에서는 새 인스턴스를 만들 때 면 수를 지정할 수 있도록 Dice
클래스를 수정합니다. Dice
클래스 정의를 변경하여 면 수를 제공할 수 있습니다. 이는 함수가 입력의 인수를 허용하는 방법과 비슷합니다.
Dice
클래스 정의를 수정하여numSides
라는 정수를 허용합니다. 클래스 내 코드는 변경되지 않습니다.
class Dice(val numSides: Int) {
// Code inside does not change.
}
- 이제
numSides
를 사용할 수 있으므로Dice
클래스 내에서sides
변수를 삭제합니다. - 또한
numSides
를 사용하도록 범위를 수정합니다.
Dice
클래스는 다음과 같이 표시됩니다.
class Dice (val numSides: Int) {
fun roll(): Int {
val randomNumber = (1..numSides).random()
return randomNumber
}
}
이 코드를 실행하면 오류가 많이 표시됩니다. Dice
클래스의 변경사항과 호환되도록 main()
을 업데이트해야 하기 때문입니다.
main()
에서 면이 6개인myFirstDice
를 만들려면 아래와 같이 이제 면 수를Dice
클래스의 인수로 제공해야 합니다.
val myFirstDice = Dice(6)
- print 문에서
sides
를numSides
로 변경합니다. - 그 아래에서
sides
를 20으로 변경하는 코드를 삭제합니다. 이 변수는 더 이상 없기 때문입니다. - 그 아래
println
문도 삭제합니다.
main()
함수는 아래의 코드와 같이 표시됩니다. 실행하면 오류가 발생하지 않습니다.
fun main() {
val myFirstDice = Dice(6)
val diceRoll = myFirstDice.roll()
println("Your ${myFirstDice.numSides} sided dice rolled ${diceRoll}!")
}
- 첫 번째 주사위 굴리기를 출력한 후 코드를 추가하여 면이 20개인
mySecondDice
라는 두 번째Dice
객체를 만들어 출력합니다.
val mySecondDice = Dice(20)
- 반환된 값을 굴리고 출력하는 print 문을 추가합니다.
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()
}
두 번째 print 문에서 랜덤 숫자를 가져오는 호출을 문자열 템플릿에 배치합니다. 첫 번째 print 문에서 동일한 작업을 실행하여 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()
함수를 사용하여 동전 던지기를 달성하는 방법은 무엇인가요?