기본 개념과 프로젝트 설계
C 언어를 학습한 후 실질적인 프로젝트 경험을 쌓고자 하는 개발자는 종종 이론 지식은 이해했지만 실제 코드를 조합해 보여줄 수 없다는 문제에 부딪힌다. 이때 가장 효과적인 방법 중 하나가 바로 단순하지만 전반적인 개념을 아우르는 소규모 프로젝트이다. 본 문서에서는 '타이핑 게임'을 중심으로, 컨솔 환경에서 동작하는 게임을 완성하는 과정을 단계별로 설명한다.
이 프로젝트는 다음과 같은 목표를 가지고 있다:
- 컨솔 출력과 좌표 기반 그리드 시스템 구현
- 사용자의 입력을 실시간으로 처리
- 문자열 기반의 게임 로직 관리
- 동적 메모리 할당 및 해제
- 모듈화된 아키텍처 설계
핵심 기능 분석
타이핑 게임의 주요 기능은 다음과 같다:
- 게임 초기화: 화면 크기 설정, 커서 숨기기, 시작 메시지 표시
- 문장 생성: 랜덤한 문장을 생성하고 화면에 표시
- 입력 감지: 키보드 입력을 실시간으로 받아들임
- 진행 상태 추적: 현재 입력 위치, 정확도, 점수 계산
- 결과 평가: 문장 완료 여부 판단, 시간 기반 스코어 산출
- 재시작 및 종료: 게임 재시작 여부 선택, 리소스 정리
이러한 기능들은 각각 독립된 모듈로 나누어 설계할 수 있으며, 이를 통해 전체 시스템의 유지보수가 용이해진다.
데이터 구조 설계
게임의 핵심은 문자열의 진행 상태를 관리하는 것이다. 이를 위해 다음과 같은 구조체를 정의한다:
typedef struct GameContext {
char targetText[256]; // 목표로 하는 문자열
int currentIndex; // 현재 입력 위치 (0부터 시작)
int totalChars; // 전체 문자 수
int correctCount; // 정확하게 입력한 글자 수
double startTime; // 게임 시작 시간 (초 단위)
double elapsedTime; // 경과 시간
int isCompleted; // 완료 여부 (1: 완료, 0: 진행 중)
} GameContext;
또한, 입력 흐름을 관리하기 위한 버퍼도 필요하다:
#define BUFFER_SIZE 256
char inputBuffer[BUFFER_SIZE];
좌표 기반 출력 시스템
컨솔 환경에서 특정 위치에 출력하려면 SetConsoleCursorPosition API를 활용해야 한다. 이를 간단한 함수로 래핑하면 다음과 같이 사용할 수 있다:
void gotoxy(int x, int y) {
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos = { (SHORT)x, (SHORT)y };
SetConsoleCursorPosition(hConsole, pos);
}
이 함수를 이용해 게임 상황을 화면에 정밀하게 배치할 수 있다. 예를 들어, 현재 진행 상태를 오른쪽 상단에 표시하는 경우:
gotoxy(70, 5);
printf("진행률: %3d%%", (int)((double)currentIndex / totalChars * 100));
입력 처리 및 상태 업데이트
사용자가 키보드를 누를 때마다 GetAsyncKeyState 함수를 사용하여 입력을 확인한다. 다음은 방향키와 일반 키를 구분하여 처리하는 예시이다:
if (GetAsyncKeyState(VK_RETURN)) {
if (currentIndex == totalChars) {
game.isCompleted = 1;
game.elapsedTime = clock() - game.startTime;
}
}
// 일반 문자 입력 처리
for (int i = 0; i < 256; ++i) {
if (GetAsyncKeyState(i) & 0x8000) {
char ch = (char)i;
if (ch >= ' ' && ch <= '~') {
if (currentIndex < totalChars && ch == game.targetText[currentIndex]) {
inputBuffer[currentIndex] = ch;
game.correctCount++;
currentIndex++;
}
}
}
}
게임 루프 구현
메인 루프는 다음과 같이 구성된다:
void runGame(GameContext* game) {
initializeGame(game);
while (!game->isCompleted) {
clearScreen();
displayStatus(game);
updateInput(game);
Sleep(50); // 50ms 대기
}
showFinalScore(game);
cleanupResources(game);
}
각 단계는 명확한 역할을 가지며, 결합도가 낮고 유지보수가 쉬운 구조를 형성한다.
리소스 관리 및 메모리 정리
동적 할당된 메모리는 반드시 해제되어야 한다. 특히 반복적으로 게임을 실행할 경우 메모리 누수가 발생할 수 있으므로, 종료 시 모든 자원을 청소하는 것이 중요하다:
void cleanupResources(GameContext* game) {
memset(game->targetText, 0, sizeof(game->targetText));
memset(inputBuffer, 0, sizeof(inputBuffer));
game->currentIndex = 0;
game->correctCount = 0;
game->isCompleted = 0;
}
확장 가능성
기본 기능 외에도 다양한 확장이 가능하다:
- 난이도 조절: 텍스트 길이, 속도, 오류 허용 범위 조정
- 랭킹 시스템: 파일 또는 데이터베이스에 최고 점수 저장
- 타이밍 기반 스코어링: 입력 속도와 정확도에 따라 점수 산출
- 다양한 테마: 영어, 한글, 숫자, 특수문자 등 다양한 입력 유형 제공
- 크로스 플랫폼 지원:
ncurses또는ANSI escape codes를 활용한 리눅스/맥 호환
학습 효과 요약
이 프로젝트를 통해 다음과 같은 핵심 기술을 익힐 수 있다:
- 구조체와 포인터를 통한 복잡한 데이터 표현
- 동적 메모리 관리 (
malloc,free) - Windows API를 활용한 컨솔 제어
- 입력 처리 및 비동기 처리 이해
- 모듈화된 프로그래밍 스타일 적용
- 시간 기반 알고리즘 설계
결론
타이핑 게임은 단순한 테스트 도구가 아니라, 실전에 가까운 소규모 시스템 개발의 좋은 사례이다. 이 과정을 통해 개발자는 단순한 문법을 넘어서, 전체 시스템의 설계와 운영 방식을 경험할 수 있으며, 이는 진정한 프로그래머로 성장하는 데 필수적인 경험이다.