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문제를 만드는 함수라는걸 생각하면 이해가 쉽다.