포스트

HP Trivia (7)

Game Logic

가장 중요한 게임로직을 만들어 본다.

우선 로직에 필요한 변수들을 먼저 만들어준다.

1
2
3
4
5
6
7
8
9
var gameScore = 0
var questionScore = 5
var recentScores = [0, 0, 0]

var activeQuestions: [Question] = []
var answeredQuestions: [Int] = []
var currentQuestion = try! JSONDecoder().decode([Question].self, from: Data(contentsOf: Bundle.main.url(forResource: "trivia", withExtension: "json")!))[0]

var answers: [String] = []
변수 이름설명
gameScore전체 게임 점수. 정답을 맞힐 때 누적된다.
questionScore문제당 초기 점수. 힌트를 보거나 오답을 선택할 때 차감된다.
recentScores최근 게임 점수 기록. 최대 3개까지 저장하여 메인 화면에 표시한다.
activeQuestions현재 게임에서 사용할 질문 목록. 선택된 책들로부터 수집된다.
answeredQuestions이미 답한 질문의 ID 목록. 같은 질문이 중복되지 않도록 관리한다.
currentQuestion현재 사용자에게 보여지는 질문. 게임 시작 시 무작위로 설정된다.
answers현재 질문에 대한 보기 항목들(정답 + 오답). 셔플하여 화면에 출력된다.

이때 currentQuestion의 경우

1
2
3
4
5
6
7
8
9
10
11
12
13
var currentQuestion = try! JSONDecoder().decode([Question].self,
    from: Data(contentsOf: Bundle.main.url(forResource: "trivia", withExtension: "json")!)
)[0]

// or

var currentQuestion: Question

init() {
    currentQuestion = try! JSONDecoder().decode([Question].self,
        from: Data(contentsOf: Bundle.main.url(forResource: "trivia", withExtension: "json")!)
    )[0]
}

두 방식 모두 사용 가능하다.

다만 아래와 같이 초기값 없이 선언만 하면 오류가 발생한다:

1
var currentQuestion: Question

class이기에 initializer가 없어서 무조건 초기값을 넣어줘야하기 때문이다. 즉, 초기값을 직접 주거나 init()에서 반드시 초기화해야 한다.

함수 만들기

이전글에 팀 프로젝트할때 게임로직을 담당했던 적이 있는데, 나름 재미있게 했었던걸로 기억한다.(물론 밤도 새곤했지만…)

그때를 회상하며 여기선 어떻게 로직을 짜는지 알아본다.

게임시작

1
2
3
4
5
6
7
8
9
10
11
func startGame() {
    for book in bookQuestions.books {
        if book.status == .active {
            for question in book.questions {
                activeQuestions.append(question)
            }
        }
    }
    
    newQuestion()
}

For loop를 통해 active인 책만 선정하여, 그걸 문제 배열에 추가해준다. 이후 newQuestion()을 호출하여 첫 문제를 시작한다.

이건 딱히 설명할게 없다.

문제생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func newQuestion() {
    if answeredQuestions.count == activeQuestions.count {
        answeredQuestions = []
    }
    
    currentQuestion = activeQuestions.randomElement()!
    
    while(answeredQuestions.contains(currentQuestion.id)) {
        currentQuestion = activeQuestions.randomElement()!
    }
    
    answers = []
    
    answers.append(currentQuestion.answer)
    
    for answer in currentQuestion.wrong {
        answers.append(answer)
    }
    
    answers.shuffle()
    
    questionScore = 5
    
}
  • 답한 문제의 갯수와 전체 문제의 갯수가 같다면 답한 문제의 배열을 초기화 해준다.
    • 왜냐면 갯수가 같다는건 이미 모든 문제를 답했다는것.
  • 사용자에게 보여질 질문은 현재 게임에서 사용할 질문 목록에서 랜덤으로 출제된다.
  • 이미 풀었던 문제인 경우 중복 방지를 위해 다시 뽑는다.
  • 정답과 오답을 하나의 배열(answers)에 합친 후 섞어준다.
  • questionScore는 새 문제마다 5점으로 초기화한다.

상세히 알아보기

문제생성의 경우는 조금 더 상세히 알아보면 좋을듯해서 조금 더 디테일하게 나눠본다.

이때 중요한건 해당 함수는 여러 문제를 미리 만들어내는 함수가 아니라, 문제 하나를 만드는 함수라는걸 알아야한다.

1. 질문 데이터 예시

JSON 예시: 문제 ID 1, 2번 일부

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
  "id": 1,
  "question": "Ever since Harry Potter was a baby, he was known in the wizarding world as what?",
  "answer": "The Boy Who Lived",
  "wrong": [
    "The Kid Who Survived",
    "The Baby Who Beat The Dark Lord",
    "The Scrawny Teenager"
  ]
}

{
  "id": 2,
  "question": "Mr. and Mrs. Dursley live at Number 4 what?",
  "answer": "Privet Drive",
  "wrong": [
    "Private Drive",
    "Privy Drive",
    "Pirate Drive"
  ]
}

2. 코드에 대한 부연설명

A. 중복 방지 및 초기화 - 이미 모든 문제를 다 풀었는지 확인

1
2
3
if answeredQuestions.count == activeQuestions.count {
    answeredQuestions = []
}
  • 예: answeredQuestions = [1, 2], activeQuestions.count == 2 → 초기화됨

B. 새 질문 선택

currentQuestion = activeQuestions.randomElement()!

  • activeQuestions 배열에서 무작위로 질문을 하나 선택함
  • 예를 들어, currentQuestion으로 id: 1 (예: “The Boy Who Lived”)가 선택될 수 있음
1
2
3
while answeredQuestions.contains(currentQuestion.id) {
    currentQuestion = activeQuestions.randomElement()!
}
  • 이미 풀었던 문제(answeredQuestions)와 겹치지 않도록 다시 무작위로 뽑음
  • 중복된 문제가 나올 경우 다시 randomElement()를 호출하며 반복
  • 이 반복문은 반드시 중복되지 않은 문제를 찾을 때까지 실행됨

C. 정답과 오답 구성

1
2
3
4
5
6
answers = []
answers.append(currentQuestion.answer)

for answer in currentQuestion.wrong {
    answers.append(answer)
}
  • id: 1일 경우 구성 결과 예시
1
2
3
4
5
6
[
  "The Boy Who Lived",
  "The Kid Who Survived",
  "The Baby Who Beat The Dark Lord",
  "The Scrawny Teenager"
]

D. 보기 셔플

answers.shuffle()

  • 위 배열은 다음과 같이 무작위로 바뀔 수 있음
1
2
3
4
5
6
[
  "The Kid Who Survived",
  "The Scrawny Teenager",
  "The Boy Who Lived",
  "The Baby Who Beat The Dark Lord"
]

E. 점수 초기화

questionScore = 5

  • 새 문제 시작 시 기본 점수로 초기화

요약
단계내용예시
중복 제거모든 문제 다 풀었을 경우 answeredQuestions 초기화[1,2] → []
질문 선택activeQuestions 중 무작위 선택id: 1
보기 구성정답 + 오답을 answers 배열에 추가[“The Boy Who Lived”, “Wrong1”, “Wrong2”, “Wrong3”]
보기 셔플정답 위치를 무작위화[“Wrong2”, “Right”, “Wrong1”, “Wrong3”] 등
점수 설정questionScore = 5항상 5점으로 시작

정답처리

1
2
3
4
5
func correct() {
    answeredQuestions.append(currentQuestion.id)
    
    gameScore += questionScore
}

맞췄을 경우 답한질문의 배열에 현재 문제의 id를 추가한다.

그리고 게임스코어도 누적하여 더해준다.

게임종료

1
2
3
4
5
6
7
8
9
func endGame() {
    recentScores[2] = recentScores[1]
    recentScores[1] = recentScores[0]
    recentScores[0] = gameScore
    
    gameScore = 0
    activeQuestions = []
    answeredQuestions = []
}
  • recentScores를 뒤로 밀고, 이번 게임의 점수를 가장 앞에 저장한다.
  • 게임 데이터를 초기 상태로 되돌린다.
  • 다음 게임을 위한 activeQuestions, answeredQuestions 초기화
  • gameScore 초기화

게임로직의 경우 newQuestion 이부분이 좀 이해하기 어려울수있으니 확실하게 이해를 하고 넘어가도록 하자.

이해하기 어렵다면, 포인트는 newQuestion 함수는 여러 문제를 미리 만들어내는 함수가 하닌, 딱 1문제를 만드는 함수라는걸 생각하면 이해가 쉽다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.