[숫자야구] 추억의 게임을 만들어 플레이해보자.

김하민·2024년 11월 7일
0
post-thumbnail

우리아이 술자리게임과
아빠의 심심풀이로 딱인 숫자야구.

한 번 만들어볼까요?

Lv. 1

1에서 9까지 서로 다른 임의의 수 3개를 정하고 맞추는 게임 (후략)

Int에 있는 .random(in:) 메서드를 활용하면 될 것 같네요...가 처음 생각이었는데,
서로 다른 임의의 수라는 조건을 보고 생각을 바꾸었습니다.

첫번째 수는 .random(in: 1...9)으로 선언해주되,
두번째와 세번째는... 코드 짜면서 설명해보죠.

일단 임의의 수를 담을 구조체와 변수를 생성해줍시다.

import Foundation

struct NumberBaseball {
    
    var answer: String
    
    init() {
        self.answer = ""
    }
}

뭐 대충 이렇게요.

이러면 main에서 var game = NumberBaseball() 이런 식으로 불러다 쓸 수 있겠죠.

계속 작성해봅시다.

새로운 게임을 시작하게 하는 메서드를 작성해보죠.

mutating func newGame() {
       
        // 새 게임의 시작이니 답을 담을 변수를 초기화해줍니다.
        answer = ""
        // 사용 가능한 숫자의 목록을 초기화해줍니다.
        var availableNumbers: [Character] = Array("0123456789")
        
        // 답이 3자리의 숫자가 될때까지 반복합니다.
        while answer.count < 3 {
            // 첫번째 자리엔 0이 올 수 없습니다.
            if answer.count == 0 {
                // 그러므로, 1부터 9까지의 범위 중 랜덤한 숫자를 뽑아,
                // randomNumber로 지정해준 뒤, answer에 추가해줍니다.
                let randomNumber = Character(Int.random(in: 1...9).description)
                answer.append(randomNumber)
                
                // 그리고는 사용 가능한 숫자의 목록에서 answer에 추가했던 숫자를 지워줍니다.
                availableNumbers.removeAll(where: { $0 == randomNumber })
            } else {
                // 두번째 자리부턴 0이 들어갈 수 있습니다.
                // 위의 availableNumber에서 0부터 9까지의 문자열을 넣은 뒤,
                // 첫번째 자리에 들어갔던 숫자를 제외해줬으니,
                // 신경쓰지 않고 랜덤 인덱스로 하나 뽑아주면 되겠습니다.
                // 아, 다만 없는 인덱스로 접근하면 런타임 에러가 나니,
                // availableNumbers의 수 보다 하나 작은 수로 랜덤한 인덱스의 범위를 제한해줍니다.
                let randomIndex = Int.random(in: 0...availableNumbers.count - 1)
                
                // 그리고는 answer 뒤에 붙여줍니다.
                answer.append(availableNumbers[randomIndex])
		}
	}
}

주석으로 설명을 갈음합니다.

네 뭐... 더 설명이 필요하진 않을 것 같네요.

main에 간단히 CLI에 표시해줄 것을 호다닥 만들어주고,

import Foundation

var game = NumberBaseball()

print("숫자를 생성하시겠습니까? Y/N")

let input = readLine()

if input == "Y" || input == "y" {
    game.newGame()
    print(game.answer)
} else {
    print("그래요 안할게요")
}

실행해주면?

잘 되는 것을 볼 수 있다.

다음 단계로 넘어가자.

Lv. 2

(대충 숫자 야구게임 로직 구현하면 된다는 내용)

그래도 모르는 사람이 있을 수 있으니 설명하자면,
3자리의 숫자를 맞추는 것이다.
근데 이제 숫자랑 자리를 맞췄으면 스트라이크, 숫자만 맞고 자리는 틀렸을 경우 볼이다.
둘 다 못 맞췄을 경우 아무것도 아니고.

예시: 임의의 숫자 = 109
109 = 3 스트라이크, 게임 종료
103 = 2 스트라이크
910 = 3 볼
190 = 1 스트라이크 2볼

뭐 대충 이런거다.

그래서 일단 NumberBaseball 구조체에 umpireCall이라는 메서드를 추가해주려고 한다.

이름에서 유추가 어느정도 가능하듯, 야구에서 주심이 스트라이크/볼 콜을 하는 것처럼,
이 메서드가 콜을 해줄거다.

(이해를 돕기 위한 심판사진)
출처 - 동아일보

심판 코드

// public인 이유는 main에서 갖다 써야 하기 때문입니다.
public func umpireCall(for input: [Character]) -> (strike: Int, ball: Int) {
    var (strike, ball) = (0, 0)
    
    for (index, char) in input.enumerated() {
        if self.answer[index] == char {
            strike += 1
        } else if self.answer.contains(char) {
            ball += 1
        }
    }
    return (strike, ball)
}

여기서 들 수 있는 의문

아니 근데 저렇게 입력값 필터링 안 하고 써도 되는 거냐고요?

당연히 안되죠.

main에서 필터링해줍시다.

let input = Array(readLine()!)

if input == game.answer {
    // 생략
}

// 첫 번째 자리가 0인 경우 필터
if input.first == "0" {
    print("첫 번째 자리는 0이 들어가지 않는다네.")
// 3자리가 아니거나 서로 중복되는 경우 필터
} else if input.count == 3
    && input[0] != input[1]
    && input[0] != input[2]
    && input[1] != input[2] {
    // 필터링 뒤에 umpireCall 호출.
    print("\(game.umpireCall(for: input).strike) 스트라이크, \(game.umpireCall(for: input).ball) 볼.")
} else {
    print("3자리의 서로 중복되지 않는 숫자만 입력이 가능하네.")
}

테스트 몇 판 돌리고 오겠습니다.

그런데 말이죠...

233...?

233????

나오면 안 되는 경우가 나와버렸습니다.

숫자야구 룰 상 같은 숫자 중복은 금지인데 말이죠.

현실에서 저런 번호 고르면 PK가 열릴 수 있으니 얼른 수정해주도록 합시다.

빨리 수정해...! 근데 왜 그렇게 된 거에요?

위에 쓰인 newGame() 메서드에 끝자락에 2번째 숫자를 넣고 나서 사용 가능한 숫자 목록에서 빼 주는 것을 까먹어버렸지 뭐에요.

하핳.

고쳐왔습니다.

while answer.count < 3 {
    // 첫번째 자리엔 0이 올 수 없습니다.
    if answer.count == 0 {
        // 그러므로, 1부터 9까지의 범위 중 랜덤한 숫자를 뽑아 randomNumber로 지정해준 뒤, answer에 추가해줍니다.
        let randomNumber = Character(Int.random(in: 1...9).description)
        answer.append(randomNumber)
        
        // 사용 가능한 숫자의 목록에서 answer에 추가했던 숫자를 지워줍니다.
        availableNumbers.removeAll(where: { $0 == randomNumber })
    } else {
        let randomIndex = Int.random(in: 0...availableNumbers.count - 1)
        let randomNumber = availableNumbers[randomIndex]
        
        answer.append(randomNumber)
        // 사용 가능한 숫자의 목록에서 answer에 추가했던 숫자를 지워줍니다.
        availableNumbers.removeAll(where: { $0 == randomNumber })
    }
}

잘 되네요.

하하.

2개의 댓글

comment-user-thumbnail
2024년 11월 9일

심판 말투가 우리아이 술자리에 어울릴 법한 게임이네요. 숫자 생성 안한다고 했을 때만 살짝 삐진 듯이 존댓말 쓰는 게 눈에 띕니다😁

1개의 답글