
공간 계획Spatial
관찰 → 사고 → 배치 → 검증
보드를 읽고, 충분히 생각하고, 수를 둡니다. Tetris, 2048, Klotski가 모두 이 위에 만들어졌습니다.
서두르지 않습니다. 머릿속으로 몇 수를 먼저 굴려 봅니다. 착수할 때마다 보드가 스스로를 재형성하고, 더 영리한 풀이가 나타납니다. 모든 수가 조용한 두뇌 체조입니다.
모든 보드·배치 플레이의 뼈대입니다. '상태 → 규칙 → 새 상태'가 이해되면 퍼즐과 전략 게임이 모두 제자리에 들어옵니다.
바로 플레이 가능한 데모
공간 계획 패턴은 보드, 그리드, 경로, 위치 관계를 핵심 데이터 구조로 합니다. 플레이어는 상태를 관찰하고, 규칙을 추론하고, 이동, 스왑, 배치를 수행해 시스템을 현재 상태에서 목표 상태로 전이시킵니다. 재미는 '아하'의 순간, 즉 제약된 공간 안에서 최적 또는 유일한 풀이를 발견하는 데서 나옵니다. 공간 게임플레이의 제1 설계 원칙은 '한눈에 이해되지만 끝없이 변화하는 규칙'입니다. 초기 규칙이 적을수록 상태 전개가 풍부해집니다. 디자이너는 종종 두 가지 큰 트레이드오프에 직면합니다. 첫째, 규칙 단순성 대 상태 공간 폭발입니다. 규칙을 단순하게 하면 플레이어가 빨리 익히지만, 지나치게 단순하면 게임플레이가 금방 질립니다. 반대로 규칙을 추가하면 깊이는 늘지만 상태 공간이 지수적으로 커져 수작업이나 알고리즘 레벨 설계가 어려워집니다. 둘째, 공간 크기 대 전략 깊이입니다. 그리드를 키우면 배치 조합과 전략 선택지가 늘지만, 너무 크면 플레이어가 전체 그림을 한눈에 보기 어려워 직관이 약해집니다. 직관에 반하는 통찰은, 이동 가능 스텝 수를 적당히 제한하면 오히려 창의성이 살아난다는 것입니다. 플레이어가 제한된 수 안에 목표를 끝내야 할 때, 더 효율적인 경로를 찾거나 그리드의 '빈' 공간을 써서 연쇄를 만들어야 하며, 이는 무제한 자유보다 숨겨진 조합의 탐험을 장려하는 경우가 많습니다.
이 패턴은 추상적 규칙을 관찰 가능하고 조작 가능한 공간 구조로 바꿉니다. 플레이어가 복잡한 계산 대신 시각·공간 직관에 기대게 하며, 퍼즐, 매치·제거, 합성, 경로 계획 같은 많은 메커니즘의 하부 프레임워크입니다. 공간 계획 게임은 보통 규칙은 단순하지만 상태 공간이 거대해 '배우기 쉽고 마스터하기 어려운' 품질을 냅니다.
공간 계획 패턴은 상태 머신, 탐색 알고리즘, 조합 폭발, 휴리스틱 평가를 이해하는 천연 교구입니다. 개발자는 복잡한 논리 문제를 직관적인 그리드 조작으로 번역하는 법과, 코드 복잡성이 아닌 레벨 설계로 경험 곡선을 다듬는 법을 배웁니다.
핵심 게임플레이 시스템 분석

- 추상적 규칙을 플레이어가 볼 수 있는 공간 행동으로 바꿉니다.
- 제한된 그리드로 풍부한 조합과 제약을 만듭니다.
- 복잡한 서사에 기대지 않고 상태 변화로 지속적인 신선함을 제공합니다.
- 그리드/그래프 데이터 구조와 순회 알고리즘을 마스터한다.
- 명확하고 확장 가능한 규칙 평가 시스템을 설계한다.
- 레벨 생성과 난이도 스케일링으로 플레이어 경험을 제어한다.
- 게임 공간(그리드, 보드, 영역)을 정의합니다.
- 상호작용 요소와 그 배치/이동 규칙을 정의합니다.
- 승패 또는 득점 조건을 정의합니다.
- 각 행동 후 규칙에 따라 공간 상태를 갱신합니다.
흔한 조합과 변형
고급 설계와 알고리즘 (접힘)클릭하여 펼치기
- 상태 공간 폭발은 풀이나 생성을 어렵게 할 수 있습니다. 휴리스틱이나 제약 솔빙이 필요할 수 있습니다.
- 지나치게 복잡한 규칙은 플레이어의 직관을 빼앗습니다. 한눈에 이해되게 유지하세요.
- 교착 감지와 힌트 메커니즘은 리텐션에 결정적입니다.
- 2D 배열 / 그래프 표현과 BFS/DFS 순회.
- 연결 요소 감지와 모양 매칭.
- 백트래킹 / 제약 충족 문제(CSP) 솔빙.
function solvable(board, depth = 0): boolean {
if (isGoal(board)) return true;
if (depth > MAX_DEPTH) return false;
for (const move of legalMoves(board)) {
const next = apply(board, move);
if (seen.has(hash(next))) continue; // prune: skip already-visited states
seen.add(hash(next));
if (solvable(next, depth + 1)) return true;
}
return false;
}