Android

(Kotlin) 개발 공부 2일차 - 로또 번호 생성기

돗개진 2024. 2. 27. 20:32

BMI 계산기에 이어서 이번에는 로또 번호 생성기를 제작해 볼 것이다. 먼저 로또 번호 생성기의 조건을 살펴보자

 

[ 로또 번호 생성 조건 ]

 

●  1~45까지 랜덤하게 공이 뽑힐 것

●  랜덤하게 출력되나 뽑힌 공은 다시 나오지 않을 것

●  사용자가 지정해서 뽑은 공의 번호와 개수를 제외하고 랜덤하게 뽑힐 것

 

● 숫자 범위별로 공의 색깔이 다른 것을 유의할 것

 

 


 

[ 로또 번호 생성기 UI - xml ]

 

ㄴ> 사용자가 지정한 번호인 6이 공으로 뽑힘
ㄴ> 사용자가 지정한 공(6번)을 포함해서 자동 생성을 시작
ㄴ> 초기화 기능을 사용해서 뽑은 공들을 전부 없앰

 

(UI 구현 - xml)

1일차에서 사용했던 Common들과 비슷하게 대부분 사용되었지만 새로 사용해 본 것이 두 가지 있다

 

 LinearLayout : ViewGroup의 하위 클래스로 가로나 세로를 순서대로 배치할 때 사용 (높이, 너비, 방향을 반드시 지정)

● NumberPicker : 숫자 범위를 지정하여 스크롤하며 숫자 하나를 선택할 수 있는 기능

 


 

(Activity 구현 - Kotlin)

// 공을 뽑는 기능을 하는 함수
    // (예외 조건: 꽉 찬 경우, 공을 5개 넘게 넣는 경우, 이미 선택한 걸 넣는 경우)
    private fun initAddButton() {
        addButton.setOnClickListener {
            when {
                didRun -> showToast("초기화 후에 시도해 주세요")
                pickNumberSet.size >= 5 -> showToast("숫자는 최대 5개까지 선택할 수 있습니다")
                pickNumberSet.contains(numPick.value) -> showToast("이미 선택된 숫자입니다")
                else -> {   // 예외 조건이 아닌 참인 경우
                    val textView = numTextViewList[pickNumberSet.size]
                    textView.isVisible = true
                    textView.text = numPick.value.toString()
                    setNumBack(numPick.value, textView)
                    pickNumberSet.add(numPick.value)
                }
            }
        }
    }

ㄴ> 위 코드는 공을 뽑는 기능을 구현하는 함수인 initAddButton이다. 이 기능은 사용자가 지정한 숫자의 공을 뽑는 것이고 예외 조건이 존재한다.

 

[ 예외 조건 ]

(공이 이미 꽉 찬 경우, 공을 5개 넘게 넣는 경우, 이미 선택한 공을 넣는 경우)

 

1. 공이 이미 꽉 찬 경우 : didRun은 공이 꽉 찼는지 아닌지를 나타내는 변수이고 기본값은 false이다. 그러나 자동 생성 이후에는 true로 변환되기 때문에 이 변수를 통해서 예외를 체크한다.

 

2. 공을 5개 넘게 넣는 경우: pickNumberSet.size 를 통해 뽑힌 숫자의 개수를 확인하며 pickNumberSet은 hashSet 타입의 자료구조이다. (hashSet: 중복을 허용하지 않으며 순서가 없는 일정한 자료구조 형태)

 

3. 이미 선택한 공을 넣는 경우: pickNumberSet.contains(numPick.value) 를 통해 pickNumberSet 안에 numPick.value 와 겹치는 값이 있는지 확인한다.

 

// 초기화 버튼을 눌렀을 때 공들을 초기화하는 함수
private fun initClearButton() {
        clearButton.setOnClickListener {
            pickNumberSet.clear()
            numTextViewList.forEach{it.isVisible = false}
            didRun = false
            numPick.value = 1

        }
    }

ㄴ> 위 코드는 초기화 버튼을 눌렀을 때 pickNumerSet을 clear()하고 forEach() 반복문을 사용하여 공들의 형태를 감춘다. 또한 다른 변수(didRun, numPick.value)들도 초기값으로 변경한다.

 

// 1부터 45까지 랜덤하게 숫자를 추출
private fun getRandom(): List<Int> {
        val numbers = (1..45).filter {it !in pickNumberSet } // filter를 통해 이미 선택된 값을 제외한 1~45
        return (pickNumberSet + numbers.shuffled().take(6-pickNumberSet.size)).sorted()
        // 사용자가 뽑은 숫자와 전체(6개) 중 사용자가 뽑은 숫자 갯수를 뺀 숫자들을 정렬
    }

ㄴ> 위 코드는 1에서 45까지 숫자들을 랜덤하게 추출하나 filter() 함수를 사용하여 이미 선택된 값을 제외하며 반환하는 값은 사용자가 뽑은 숫자 + 전체(6개) - 사용자가 뽑은 숫자와 개수(n개)를 정렬(sorted())하여 반환한다.

 

// 숫자 범위에 따라 공의 색깔을 지정하는 함수
private fun setNumBack(number: Int, textView: TextView) {
        val background = when(number) {
            in 1..10 -> R.drawable.circle_yellow
            in 11..20 -> R.drawable.circle_blue
            in 21..30 -> R.drawable.circle_red
            in 31..40 -> R.drawable.circle_gray
            else -> R.drawable.circle_green
        }
        textView.background = ContextCompat.getDrawable(this, background)
    }

ㄴ> 위 코드는 앞서 설명한 조건에 맞게 로또 번호의 범위에 따라 공의 색깔을 지정한다.

 

// Toast 메세지를 띄우는 명령어를 매번 작성하는 번거로움을 없애기 위한 함수
private fun showToast(message: String) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
    }

ㄴ> 위 코드는 로또 생성기와 상관은 없지만 Toast 메세지를 띄우는 명령어를 매번 작성하지 않고 메소드 형식으로 사용할 수 있도록 만든 함수이다.

 


 

[ 사용된 Kotlin 문법 간단 정리 (기억할 만한 것 위주) ]

 

  • by lazy 와 lateinit
    private val clearButton by lazy { findViewById<Button>(R.id.btn_clear) }
    private val addButton by lazy { findViewById<Button>(R.id.btn_add) }
    private val runButton by lazy { findViewById<Button>(R.id.btn_run) }
    private val numPick by lazy { findViewById<NumberPicker>(R.id.np_num) }

ㄴ> 위 코드를 보면 변수를 선언할 때 by lazy 라는 구문이 붙은 것을 볼 수 있는데 이 구문의 역할은 쉽게 설명하면 늦은 초기화를 위해 사용되는 구문이다. Kotlin은 언어 특성상 null을 허용하지 않기 때문에 초기화가 필수적이다. 이런 초기화를 늦추기 위해 사용하는 것 ( by lazy는 불변 변수인 val 를 사용할 때 쓰고 / lateinit은 가변 변수인 var 를 사용할 때 쓴다 )