GooApp - 퍼즐 교착상태 문제점
2025-06-02
퍼즐을 만들며 발생한 문제점 해결과정
상황
퍼즐을 만들면서 교착상태에 빠져 더이상 진행할 수 없는 문제가 발생했다.
원하는 기능은 5:5로 문제와 정답을 연결하는 기능이다.
- 문제, 정답이 이전과 다른 랜덤한 위치에 떠야 한다.
- 항상 연결할 수 있는 문제-정답 조합이 하나 이상 남아있어야 한다.

첫 접근 방식
처음에는 문제-정답이 모두 이어지는 그룹을 계속 쌓아가는 스택 방식을 생각했다.
- 버튼의 개수대로 문제들을 모아놓은 그룹을 만든다. - 이 그룹이 1개 층이 된다.
- 문제를 맞추면 다음 스택에 있는 문제가 올라온다.
- 윗층을 모두 맞추면 아래층으로 완전히 넘어가면서 항상 조합이 나오도록 유지된다.
시각화 예시
- 맞춘 문제는 사라진다.
- 다음 스택의 문제들이 보여진다.

예시 상황
하나의 정답에 연결될 수 있는 두 개의 정답이 존재할 수 있다.

다른 층의 조합을 연결한다면 교착상태가 발생해 더이상 진행할 수 없어진다.

문제와 정답에 중복이 없다면 교착이 발생하지 않지만 구구단의 특성상 중복을 제거할수는 없다.
ex) 6x4, 4x6, 8x3, 3x8 = 24
해결
Queue에서 문제를 생성해서 List로 이동해 보여지는 방식을 떠올렸다.
문제Queue, 정답Queue / 문제List, 정답List 두 종류씩 만든다.
알고리즘
문제List 채우기
- 문제List 빈 칸에 문제Queue의 문제를 채운다. (문제Q -> 문제L)
- 빈 칸으로 이동시킬 문제가 없다면 새로운 문제를 생성하고, 정답을 정답Queue 랜덤 위치에 추가한다.
(문제L : 문제 생성 / 정답Q : 정답 생성)
정답List 채우기
- 정답List 빈 칸에 정답Queue의 정답을 채운다. (정답Q -> 정답L)
- 빈 칸으로 이동시킬 정답이 없다면 새로운 문제를 생성하고, 문제는 문제Queue 랜덤 위치에 추가한다.
(정답L : 정답 생성 / 문제Q : 문제 생성)
-> 문제List, 정답List가 모두 채워졌고, List들을 화면에 보여준다.
게임을 진행하며 반복한다.
설명
먼저 문제List를 모두 채우고, 조합이 생성될 때 정답Queue를 랜덤하게 채운다.
그 다음 정답List를 모두 채우고 마찬가지로 조합이 생성될 때 문제Queue를 랜덤하게 채운다.
- 초기 상태

-
문제 이동

-
문제 채우기

-
정답 이동

-
정답 채우기

코드
export const queueAlgorithm = (
questQueue: QuestArray[],
questList: QuestArray[]
): [QuestArray[], QuestArray[]] => {
// 큐와 리스트를 분할해 사용함
const QQueue = [...questQueue[0]];
const AQueue = [...questQueue[1]];
const QList = [...questList[0]];
const AList = [...questList[1]];
// 문제_리스트의 빈칸을 모두 채운다.
QList.forEach((Q, i) => {
if (Q === null && QQueue[i] === null) {
// 문제가 비어있고 문제_큐의 해당 칸이 비어있다면 - 문제를 생성한다.
const [newQuest, newAnswer] = questGenerator();
// 문제의 정답을 정답_큐의 랜덤 위치에 추가한다.
insertRandom(AQueue, newAnswer);
QList[i] = newQuest;
} else if (Q === null && QQueue[i] !== null) {
// 문제가 비어있고 문제_큐의 해당 칸이 존제한다면 - 문제를 채운다.
const temp = QQueue[i];
QQueue[i] = null;
QList[i] = temp;
}
});
// 정답_리스트의 빈칸을 모두 채운다.
AList.forEach((A, i) => {
if (A === null && AQueue[i] === null) {
// 정답이 비어있고 정답_큐의 해당 칸이 비어있다면 - 문제를 생성한다.
const [newQuest, newAnswer] = questGenerator();
// 문제를 문제_큐의 랜덤 위치에 추가한다.
insertRandom(QQueue, newQuest);
AList[i] = newAnswer;
} else if (A === null && AQueue[i] !== null) {
// 정답이 비어있고 정답_큐의 해당 칸이 존제한다면 - 정답을 채운다.
const temp = AQueue[i];
AQueue[i] = null;
AList[i] = temp;
}
});
// 큐와 리스트 다시 병합해서 반환함
const newQueue = [QQueue, AQueue];
const newList = [QList, AList];
return [newQueue, newList];
};
결론
알고리즘을 만들 때는 개발을 편하게 하기 위해서 노트에 그림을 그려가며 흐름을 정해놔야겠다.
그래도 문제를 빨리 발견해서 바로 해결할 수 있었다.
문제가 생기고 막힐 때는 잠시 쉬어가며, 어쩌다 이 지경이 되었는지 복기해보자.
일단 밥을 먹다보면 해결 방법이 떠오를 것이다.
