Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
## 코드 리팩토링
기존 : 절차지향, 함수형
변경 : 객체지향

## 구성
- Game : 시작 메뉴(게임 실행, 기록 관리, 종료)

- GameInput : 입력을 받고 오류 검증 및 출력
- 입력에 관한 검사

- GameRule : 구현한 야구 게임의 규칙
- 정답 생성, 저장
- 스트라이크, 볼 결과 판정

- main : 실제 게임을 동작하기 위한 코드


### 객체 생성(Class vs Struct) :
Game, GameRule, main에서는 class를 통한 객체를 생성하고있습니다.(GameInput만 struct를 이용해서 생성)
큰 이유는 존재하지 않습니다. 본 코드에서 상속과 관련한 기능을 사용하지는 않지만, Game에서는 사용자가 입력을 몇 회하였는지를 기록하고 저장해야하며, GameRule과 main에서도 각각 생성된 난수를 저장, 게임의 기록을 저장하고 있습니다. 반면, GameInput은 입력 값에 관한 결과를 반환할 뿐, 저장하는 동작이 필요가 없어서 struct를 사용하는 것이 낫다고 판단했습니다.


## 재제출 시 변경사항
[1.4] 검증 규칙이 변경될 때(예: 자리수 4자리로 확장), 수정 범위가 예상 가능하다
- 검증 규칙이 변경될 경우, 검증 규칙에 관한 로직이 구현되어있는 GameRule.swift의 randomAnswer || randomAnswer2의 함수 중 선택하여 수정이 가능하다. 만약, 문제의 기준사항처럼 만약 자리수를 확장하게 될 경우 2가지의 조건을 통해 구현할 수 있는데, 방법1로는 randomAnswer 함수와 randomAnswer2 함수 선언시 사용한 while 구문을 수정하면 된다. 또한, 현재의 코드에 적용되어 있지 않은 방법으로 shuffle을 통해 조건에 맞는 배열을 선언한 뒤 0~3까지의 인덱스를 잘라서 규칙을 생성하는 방법으로도 구현이 가능하다. 물론, 규칙의 변경에 따라 사용자의 입력을 확인받는 코드 또한 3자리가 아니라 4자리로 변경해야 한다.

[2.6] 아래 상황이 오면, 내 코드에서 “어디를 바꿔야 하는지” 바로 말할 수 있나요?
- [ ] 숫자 자리수를 3 → 4로 바꾼다 : [1.4] 해설을 참고할 수 있을 것 같다.
- [ ] 중복 허용 규칙이 생긴다/사라진다 : 현재는 중복 허용 규칙을 통해 만들어 둔 것이다. 중복을 검증하는 로직으로 2가지 방법을 사용했는데, randomAnswer에서는 저장되는 값을 Set으로 검증하여 count를 추가하기에 중복이 불가능하도록 구현되어있다. 이를 Set을 통해 검증하는 로직을 배제한다면 중복 구현도 가능할 것이다. 또한, randomAnswer2에서는 result에 contain되어있는지 확인 후 숫자를 넣도록 구현해두었다. 이로 인해 중복이 불가능하며, 위 사항을 제거한다면 중복 허용 기능을 추가할 수 있다.
- [ ] “Nothing” 대신 “0S 0B”로 출력 포맷이 바뀐다 : Game.swift의 hint 함수를 수정하여 else if를 사용할 필요가 없어질 것 같다. 현재 구현되어 있는 방식은 조건을 strike가 3일 경우를 제외하고 나머지의 경우를 총 4번의 조건을 확인하여 출력을 하는데, Nothing을 출력할 필요없이 0S 0B로 출력할 것이라면, 정답을 맞히는 상황을 제외하면 모든 상황에 nS mB로 출력하도록 구현하는게 낫지 않을까 생각한다. (ex. 2스트라이크 1볼일 경우 : 2S 1B로 형식을 맞춰서 출력)
- [ ] 난이도 선택(자리수/허용 범위)을 메뉴에서 받는다 : 허용 범위에 관해서는 생각해본 적이 없다. 정확히 어떤 기능을 예시로 드는 것인지 잘 모르겠다. 다만 자리수를 입력받아서 확인해보는 시스템은 생각해 본 적이 있다. 사용자에게 입력받는 것과 마찬가지로 숫자 자리수를 입력받아서 GameRule.swift의 정답 생성용 함수를 수정한다. 현재는 count가 3보다 작은 경우로 하드코딩되어 있지만, 이 부분을 사용자에게 입력받은 n을 이용하도록 구현한다면 자리수에 관한 기능 수정도 가능할 것이다.

[2.7] 고차함수(map/filter/reduce)를 “가독성 향상” 목적으로 자연스럽게 사용했거나, 사용하지 않았다면 그 이유를 설명할 수 있다
- 기본적으로 이번 과제에서는 고차함수만을 사용해서 구현하도록 노력하지는 않았다. 다만, 사용하지 않은 경우 코드의 길이가 길어지는 경우와 구현하던 당시의 상황에 본인의 실력을 테스트해보고자 했던 경우에는 무리해서 사용한 감도 없지 않아 있다. 예를 들어, 사용자의 입력을 검증하는 구문을 구현할 때 guard let 구문에 한번에 입력값에 관한 조건을 넣어보고자 했다. 무리해서 사용하였기에 다른 방도를 찾지 못하고 결국 Optional로 처리하여 묶어주는 방법밖에 생각하지 못했으나, 튜터님의 피드백을 통해 map과 compactMap을 통해 구문을 보다 깔끔하게 처리하는 방법을 알게 되었다.

[2.9] 에러 처리를 `do-catch` + 커스텀 에러로 정리했거나, 사용하지 않았다면 “이 과제에선 과했다/단순 Bool이 더 적절했다” 등 판단 근거가 있다
- 에러 처리와 프로그램 실행 시 나타나는 게임 선택화면 구현 시, 열거형으로 케이스를 구분하여 작성하는 방법도 생각해보았지만, 본인은 현재 프로젝트에서 달리 해야할 이유를 찾지 못했다. 단순하게, 요구사항을 충족하고자 했던 생각도 있지만, 선택할 수 있는 케이스가 많지 않았으며 관리에 요구되는 조건들도 없었기에 열거형보다 기본적인 switch문을 통해 이용하고자 했으며 사용자의 입력값에 관한 에러 처리에 대해서도 조건에 걸릴 경우로 처리하는 게 편하고 나을 것이라 판단했다.
48 changes: 48 additions & 0 deletions baseballgame/Game.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// Game.swift
// aa
//
// Created by 손영빈 on 1/15/26.
//

// Game 소스코드는 GameRule, GameInput의 객체를 이용하여 게임 내부에서 동작하는 기능들을 구현하는 객체를 만들었습니다. GameRule에서 계산한 힌트 정보를 hint 함수를 통해서 출력하도록 구현했습니다. 또한, 정답을 맞췄을 경우도 hint 함수 내부에서 동작하도록 구현할 수 있으나, 정답일 경우만 play 함수에 넣어둔 이유는 hint 함수는 함수명 그 자체로 힌트만을 출력하는 함수를 구현하고자 생각하여 분리하였습니다.
import Foundation

class Game{
private let rule = GameRule()
private let gameinput = GameInput()

func play() -> Int{
rule.randomAnswer2()
var totalCount = 0

while true {
guard let inputNumber = gameinput.getInput() else {
continue
}

totalCount += 1 //기록 저장용 Count + 1

let score = rule.gameScore(inputChars: inputNumber)

if score.strike == 3 {
print("정답입니다.")
return totalCount
} else {
hint(strike : score.strike, ball: score.ball)
}
}
}
/* (Lv.2) 3. 힌트 출력 */
func hint(strike: Int, ball: Int){
if strike >= 1 && ball >= 1 {
print("\(strike)스트라이크 \(ball)볼")
}else if strike >= 1 {
print("\(strike)스트라이크")
}else if ball >= 1 {
print("\(ball)볼")
}else {
print("Nothing")
}
}
}
30 changes: 30 additions & 0 deletions baseballgame/GameInput.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// GameInput.swift
// aa
//
// Created by 손영빈 on 1/15/26.
//


// GameIput 소스코드는 사용자의 입력을 받고, 입력된 값이 정상적인지 판단하기 위한 객체를 만들었습니다. 조건(1. 숫자가 아닌 다른 값의 입력, 2. 중복된 값의 입력)을 통해 정상적인 값이 아니라는 것이 판단되면 nil을 반환하여 game의 while 구문 guard let에서 걸리게 되어 입력을 다시 받도록 구현되어있습니다. 또한, guard let에 조건을 모두 넣어보고자 Optional을 감싸서 compactMap을 사용하였습니다. map대신 compactMap을 사용하고자 한 이유에 대해서는 아래 코드의 주석을 참고해주시면 될 것 같습니다.

import Foundation

struct GameInput {
func getInput() -> [Int]? {
/* (Lv.2) 1. 사용자 입력 검증 */
print("숫자를 입력하세요: ", terminator: "\n")
guard let num = readLine().map({ $0.compactMap(\.wholeNumberValue) }), //숫자 외의 다른 값이 들어왔는지 확인, map 대신 compactMap 사용 : map 사용시 nil로 처리 되기 때문에 처리가 바르게 일어나지않음(ex. 1ab입력시 num = [1,nil,nil] -> 중복으로 처리됨
num.count == 3
else {
print("올바르지 않은 입력값입니다.")
return nil
}

if Set(num).count != 3 { //중복된 값이 있는지 확인
print("올바르지 않은 입력값입니다.(중복)")
return nil
}
return num
}
}
60 changes: 60 additions & 0 deletions baseballgame/GameRule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//
// GameRule.swift
// aa
//
// Created by 손영빈 on 1/15/26.
//


// GameRule 소스코드는 게임의 규칙을 생성하기 위한 객체를 만들었습니다. 때문에, 기존에 합쳐져있던 힌트의 생성과 출력은 분리하여 본 파일에서는 힌트 결과 생성만을 구현합니다.
import Foundation

class GameRule {
private var result: [Int] = []

/* (Lv.1) 1. 1~9 서로 다른 난수 3개 생성 -> Set 사용 -> 문제점 : 정답 생성용 Set, 저장용 Array 별도로 필요*/
func randomAnswer(){
var answer: Set<Int> = []
var result: [Int] = []

while answer.count < 3 { //정답 생성용(Set 사용) : 같은 수 반복 x
let num = Int.random(in: 1...9)
answer.insert(num)
}
result = Array(answer) // 정답 저장용
// print(answer)
// print(result)
self.result = result // 변경사항 -> return 사용하지않고 객체에 result로 담아둠
}

/* (Lv 3.) 1. 첫 자리(1~9), 나머지(0~9): Set 사용하지 않고 contains로 조건 사용 */
func randomAnswer2(){
var result: [Int] = []

let fst = Int.random(in: 1...9)
result.append(fst)
while result.count < 3{
let rndnum = Int.random(in: 0...9)
if !result.contains(rndnum){
result.append(rndnum)
}
}
// print(result)
self.result = result // 변경사항 -> return 사용하지않고 객체에 result로 담아둠
}

/* (Lv.2) 2. 힌트 생성, 계산 -> 출력은 game.hint를 이용하여 사용하도록 분리 */
func gameScore(inputChars: [Int]) -> (strike: Int, ball: Int){
var strike: Int = 0
var ball: Int = 0

for i in 0..<3 {
if result[i] == inputChars[i] { // 변경사항 -> inputChars는 배열이라 inputChars[i]로 접근 가능
strike += 1
} else if result.contains(inputChars[i]) {
ball += 1
}
}
return (strike, ball)
}
}
61 changes: 0 additions & 61 deletions baseballgame/game.swift

This file was deleted.

51 changes: 50 additions & 1 deletion baseballgame/main.swift
Original file line number Diff line number Diff line change
@@ -1 +1,50 @@
startGame()
//
// main.swift
// aa
//
// Created by 손영빈 on 1/15/26.
//

// Main 소스코드는 게임의 시작선택메뉴 나타내기 위한 객체와 실행을 위한 app인스턴스생성 및 실행을 위해 만들었습니다.
import Foundation

class Main{
private var scoreRecord: [Int] = []
private let game = Game()

func startGame(){

/* (Lv 4.) 1. 프로그램 시작 시 안내 문구 출력 */
while true {
print("""
환영합니다! 원하시는 번호를 입력해주세요.
1. 게임 시작하기 2. 게임 기록 보기 3. 종료하기
""")
guard let input = readLine()
else { continue }

switch input {
/* (Lv 4.) 2. 게임 시작하기 선택 시 play로 연결 */
case "1":
let score = game.play()
scoreRecord.append(score) // score을 Record에 저장
/* (Lv 5.) 1. 게임 기록 보기 선택 시 시도 횟수 출력 */
case "2":
print("게임 기록 보기")
for (idx, score) in scoreRecord.enumerated() { //고차함수 enumerated를 사용하여 idx: 인덱스 score: 점수 반환 후 출력
print("\(idx + 1)번째 게임 : 시도 횟수 - \(score)")
}
/* (Lv 6.) 1. 종료하기 선택 시 프로그램 종료(기록 초기화) */
case "3":
print("게임을 종료합니다.")
return
/* (Lv 6.) 2. 이외 입력값에 대한 오류 출력 */
default:
print ("올바른 숫자를 입력해주세요!")
}
}
}
}

let app = Main()
app.startGame()
39 changes: 0 additions & 39 deletions baseballgame/random.swift

This file was deleted.

42 changes: 0 additions & 42 deletions baseballgame/startinfo.swift

This file was deleted.