본캠프 3주차 수요일 TIL

호씨·2024년 11월 6일
0

금일은 숫자야구 문제를 더 푸는 시간을 가졌다.

1. lv2 정답을 맞추기 위해 3자리수를 입력하고 힌트받기

BaseballGame.Swift

// BaseballGame.Swift
import Foundation

class BaseballGame {
    private var targetNumber: [Int]
    private var attempts: Int
    
    init() {
        self.targetNumber = BaseballGame.generateRandomNumber()
        self.attempts = 0
    }
    
    // 랜덤 3자리 숫자 생성
    private static func generateRandomNumber() -> [Int] {
        var numbers = Array(0...9)
        var result: [Int] = []
        
        // 첫 번째 숫자는 0이 아닌 숫자여야 함
        let firstNum = Int.random(in: 1...9)
        result.append(firstNum)
        numbers.remove(at: firstNum)
        
        // 나머지 두 숫자 선택
        for _ in 0..<2 {
            let index = Int.random(in: 0..<numbers.count)
            result.append(numbers[index])
            numbers.remove(at: index)
        }
        
        return result
    }
    
    // 사용자 입력값 검증
    private func validateInput(_ input: String) -> [Int]? {
        guard input.count == 3,
              let number = Int(input),
              number >= 100 && number <= 999 else {
            return nil
        }
        
        let digits = input.map { Int(String($0))! }
        
        // 중복된 숫자가 있는지 확인
        guard Set(digits).count == 3 else {
            return nil
        }
        
        return digits
    }
    
    // 스트라이크와 볼 계산
    private func checkNumbers(guess: [Int]) -> (strikes: Int, balls: Int) {
        var strikes = 0
        var balls = 0
        
        for i in 0..<3 {
            if guess[i] == targetNumber[i] {
                strikes += 1
            } else if targetNumber.contains(guess[i]) {
                balls += 1
            }
        }
        
        return (strikes, balls)
    }
    
    // 게임 진행을 위한 메서드
    func makeGuess(_ inputNumber: String) -> GameResult {
        guard let guess = validateInput(inputNumber) else {
            return .invalidInput
        }
        
        attempts += 1
        let result = checkNumbers(guess: guess)
        
        if result.strikes == 3 {
            return .gameWon(attempts: attempts)
        }
        
        return .ongoing(strikes: result.strikes, balls: result.balls)
    }
    
    // 게임 결과를 나타내는 열거형
    enum GameResult {
        case invalidInput
        case ongoing(strikes: Int, balls: Int)
        case gameWon(attempts: Int)
    }
}

main.Swift

import Foundation

// 게임 실행
func playGame() {
    print("숫자야구 게임을 시작합니다.")
    let game = BaseballGame()
    
    while true {
        print("3자리 숫자를 입력하세요: ", terminator: "")
        guard let input = readLine() else { continue }
        
        let result = game.makeGuess(input)
        
        switch result {
        case .invalidInput:
            print("올바른 3자리 숫자를 입력해주세요. (중복되지 않은 숫자)")
        case .ongoing(let strikes, let balls):
            print("\(strikes)스트라이크 \(balls)볼")
        case .gameWon(let attempts):
            print("축하합니다! \(attempts)번 만에 맞추셨습니다.")
            return
        }
    }
}

// 게임 시작
playGame()

해당부분은 게임을 시작 및 프린트문으로 유저에게 보여주는 main.Swift파일과 게임로직을 실행하는 BaseballGame.Swift파일 두개로 나누어서 작성했다.

2. lv3 정답이 되는 숫자를 0에서 9까지의 서로 다른 3자리의 숫자로 바꾸기

  • 맨 앞자리에 0이 오는 것은 불가능합니다

    BaseballGame.Swift 수정본

import Foundation

class BaseballGame {
    private var targetNumber: [Int]
    private var attempts: Int
    
    init() {
        self.targetNumber = BaseballGame.generateRandomNumber()
        self.attempts = 0
    }
    
    // 랜덤 3자리 숫자 생성 - 개선된 버전
    private static func generateRandomNumber() -> [Int] {
        // 1-9까지의 숫자로 배열 생성 (첫 자리는 0이 올 수 없으므로)
        var availableNumbers = Array(1...9)
        // 첫 번째 숫자 선택 및 제거
        let firstIndex = Int.random(in: 0..<availableNumbers.count)
        let firstNumber = availableNumbers.remove(at: firstIndex)
        
        // 두 번째, 세 번째 숫자를 위해 0 추가
        availableNumbers.append(0)
        
        // 두 번째 숫자 선택 및 제거
        let secondIndex = Int.random(in: 0..<availableNumbers.count)
        let secondNumber = availableNumbers.remove(at: secondIndex)
        
        // 세 번째 숫자 선택
        let thirdIndex = Int.random(in: 0..<availableNumbers.count)
        let thirdNumber = availableNumbers[thirdIndex]
        
        return [firstNumber, secondNumber, thirdNumber]
    }
    
    // 사용자 입력값 검증
    private func validateInput(_ input: String) -> [Int]? {
        guard input.count == 3,
              let number = Int(input),
              number >= 100 && number <= 999 else {
            return nil
        }
        
        let digits = input.map { Int(String($0))! }
        
        // 중복된 숫자가 있는지 확인
        guard Set(digits).count == 3 else {
            return nil
        }
        
        return digits
    }
    
    // 스트라이크와 볼 계산
    private func checkNumbers(guess: [Int]) -> (strikes: Int, balls: Int) {
        var strikes = 0
        var balls = 0
        
        for i in 0..<3 {
            if guess[i] == targetNumber[i] {
                strikes += 1
            } else if targetNumber.contains(guess[i]) {
                balls += 1
            }
        }
        
        return (strikes, balls)
    }
    
    // 게임 진행을 위한 메서드
    func makeGuess(_ inputNumber: String) -> GameResult {
        guard let guess = validateInput(inputNumber) else {
            return .invalidInput
        }
        
        attempts += 1
        let result = checkNumbers(guess: guess)
        
        if result.strikes == 3 {
            return .gameWon(attempts: attempts)
        }
        
        return .ongoing(strikes: result.strikes, balls: result.balls)
    }
    
    // 디버깅을 위한 정답 확인 메서드 (필요시 사용)
    func getTargetNumber() -> String {
        return targetNumber.map(String.init).joined()
    }
    
    // 게임 결과를 나타내는 열거형
    enum GameResult {
        case invalidInput
        case ongoing(strikes: Int, balls: Int)
        case gameWon(attempts: Int)
    }
}

generateRandomNumber이라는 함수명을 통해서 첫자리에 0이 안오게 로직을 짰다.

3. lv4 프로그램을 시작할 때 안내문구를 보여주기

// 예시
환영합니다! 원하시는 번호를 입력해주세요
1. 게임 시작하기  2. 게임 기록 보기  3. 종료하기

게임 메뉴 시스템 추가

import Foundation

/// 게임 기록을 저장하는 구조체
struct GameRecord {
    let date: Date
    let attempts: Int
}

/// 게임의 전체 기록을 관리하는 클래스
class GameHistory {
    private var records: [GameRecord] = []
    
    /// 새로운 게임 기록 추가
    func addRecord(attempts: Int) {
        records.append(GameRecord(date: Date(), attempts: attempts))
    }
    
    /// 모든 게임 기록 조회
    func showRecords() {
        if records.isEmpty {
            print("아직 게임 기록이 없습니다.")
            return
        }
        
        print("\n[ 게임 기록 ]")
        print("날짜\t\t\t시도 횟수")
        print("----------------------------------------")
        
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
        
        for record in records {
            print("\(dateFormatter.string(from: record.date))\t\(record.attempts)회")
        }
        print("----------------------------------------")
    }
}

/// 메뉴 옵션을 나타내는 열거형
enum MenuOption: Int {
    case startGame = 1
    case showRecords = 2
    case exit = 3
}

/// 게임 메뉴를 표시하고 사용자 입력을 받는 함수
func showMenu() -> MenuOption? {
    print("\n[ 숫자 야구 게임 ]")
    print("1. 게임 시작하기")
    print("2. 게임 기록 보기")
    print("3. 종료하기")
    print("선택해주세요: ", terminator: "")
    
    guard let input = readLine(),
          let number = Int(input),
          let option = MenuOption(rawValue: number) else {
        return nil
    }
    
    return option
}

/// 숫자야구 게임을 실행하는 메인 함수
func playGame() -> Int? {
    print("\n게임을 시작합니다!")
    print("서로 다른 3자리 숫자를 맞혀보세요.")
    print("각 숫자는 0과 9 사이이며, 첫 번째 자리는 0이 될 수 없습니다.")
    
    let game = BaseballGame()
    
    // 테스트/디버깅용 정답 출력
    print("정답: \(game.getTargetNumber())")
    
    while true {
        print("\n3자리 숫자를 입력하세요: ", terminator: "")
        guard let input = readLine() else { continue }
        
        let result = game.makeGuess(input)
        
        switch result {
        case .invalidInput:
            print("올바른 3자리 숫자를 입력해주세요. (중복되지 않은 숫자)")
            
        case .ongoing(let strikes, let balls):
            if strikes == 0 && balls == 0 {
                print("아웃!")
            } else {
                print("\(strikes)스트라이크 \(balls)볼")
            }
            
        case .gameWon(let attempts):
            print("축하합니다! \(attempts)번 만에 맞추셨습니다.")
            return attempts
        }
    }
}

// 메인 프로그램 실행
let gameHistory = GameHistory()

while true {
    // 메뉴 표시 및 사용자 입력 받기
    guard let option = showMenu() else {
        print("잘못된 입력입니다. 1-3 사이의 숫자를 입력해주세요.")
        continue
    }
    
    // 선택된 메뉴 실행
    switch option {
    case .startGame:
        // 게임 실행 및 결과 저장
        if let attempts = playGame() {
            gameHistory.addRecord(attempts: attempts)
        }
        
    case .showRecords:
        gameHistory.showRecords()
        
    case .exit:
        print("\n게임을 종료합니다.")
        exit(0)
    }
}

맨처음 playGame함수안에 int값을 받는것을 기준으로 각각 1,2,3 값을 집어넣게 만들었으며

// 테스트/디버깅용 정답 출력
print("정답: (game.getTargetNumber())")
코드를 집어넣어서 각 문제간에 빠르게 확인을 할 수 있게 집어넣었다.

4. lv5 2번 게임 기록 보기의 경우 완료한 게임들에 대해 시도 횟수를 보여주기

// 예시
환영합니다! 원하시는 번호를 입력해주세요
1. 게임 시작하기  2. 게임 기록 보기  3. 종료하기
2 // 2번 게임 기록 보기 입력

< 게임 기록 보기 >
1번째 게임 : 시도 횟수 - 14
2번째 게임 : 시도 횟수 - 9
3번째 게임 : 시도 횟수 - 12

게임메뉴 세분화

import Foundation

/// 게임 기록을 저장하는 구조체
struct GameRecord {
    let attempts: Int
}

/// 게임의 전체 기록을 관리하는 클래스
class GameHistory {
    private var records: [GameRecord] = []
    
    /// 새로운 게임 기록 추가
    func addRecord(attempts: Int) {
        records.append(GameRecord(attempts: attempts))
    }
    
    /// 모든 게임 기록 조회
    func showRecords() {
        if records.isEmpty {
            print("\n아직 게임 기록이 없습니다.")
            return
        }
        
        print("\n< 게임 기록 보기 >")
        for (index, record) in records.enumerated() {
            print("\(index + 1)번째 게임 : 시도 횟수 - \(record.attempts)")
        }
        print()
    }
}

/// 메뉴 옵션을 나타내는 열거형
enum MenuOption: Int {
    case startGame = 1
    case showRecords = 2
    case exit = 3
}

/// 게임 메뉴를 표시하고 사용자 입력을 받는 함수
func showMenu() -> MenuOption? {
    print("\n[ 숫자 야구 게임 ]")
    print("1. 게임 시작하기")
    print("2. 게임 기록 보기")
    print("3. 종료하기")
    print("선택해주세요: ", terminator: "")
    
    guard let input = readLine(),
          let number = Int(input),
          let option = MenuOption(rawValue: number) else {
        return nil
    }
    
    return option
}

/// 숫자야구 게임을 실행하는 메인 함수
func playGame() -> Int? {
    print("\n게임을 시작합니다!")
    print("서로 다른 3자리 숫자를 맞혀보세요.")
    
    let game = BaseballGame()
    
    // 정답 확인하기 (테스트용) 추후 삭제예정
    print("정답: \(game.getTargetNumber())")
    
    while true {
        print("\n3자리 숫자를 입력하세요: ", terminator: "")
        guard let input = readLine() else { continue }
        
        let result = game.makeGuess(input)
        
        switch result {
        case .invalidInput:
            print("올바른 3자리 숫자를 입력해주세요. (중복되지 않은 숫자)")
            
        case .ongoing(let strikes, let balls):
            if strikes == 0 && balls == 0 {
                print("아웃!")
            } else {
                print("\(strikes)스트라이크 \(balls)볼")
            }
            
        case .gameWon(let attempts):
            print("축하합니다! \(attempts)번 만에 맞추셨습니다.")
            return attempts
        }
    }
}

// 메인 프로그램 실행
let gameHistory = GameHistory()

while true {
    // 메뉴 표시 및 사용자 입력 받기
    guard let option = showMenu() else {
        print("잘못된 입력입니다. 1-3 사이의 숫자를 입력해주세요.")
        continue
    }
    
    // 선택된 메뉴 실행
    switch option {
    case .startGame:
        // 게임 실행 및 결과 저장
        if let attempts = playGame() {
            gameHistory.addRecord(attempts: attempts)
        }
        
    case .showRecords:
        gameHistory.showRecords()
        
    case .exit:
        print("\n게임을 종료합니다.")
        exit(0)
    }
}
profile
이것저것 많이 해보고싶은 사람

0개의 댓글