우리아이 술자리게임과
아빠의 심심풀이로 딱인 숫자야구.
한 번 만들어볼까요?
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("그래요 안할게요")
}
실행해주면?
잘 되는 것을 볼 수 있다.
다음 단계로 넘어가자.
(대충 숫자 야구게임 로직 구현하면 된다는 내용)
그래도 모르는 사람이 있을 수 있으니 설명하자면,
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 })
}
}
잘 되네요.
하하.
심판 말투가 우리아이 술자리에 어울릴 법한 게임이네요. 숫자 생성 안한다고 했을 때만 살짝 삐진 듯이 존댓말 쓰는 게 눈에 띕니다😁