컨솔 기반의 C 언어로 구현하는 타이핑 게임: 데이터 구조와 제어 흐름의 통합

기본 개념과 프로젝트 설계

C 언어를 학습한 후 실질적인 프로젝트 경험을 쌓고자 하는 개발자는 종종 이론 지식은 이해했지만 실제 코드를 조합해 보여줄 수 없다는 문제에 부딪힌다. 이때 가장 효과적인 방법 중 하나가 바로 단순하지만 전반적인 개념을 아우르는 소규모 프로젝트이다. 본 문서에서는 '타이핑 게임'을 중심으로, 컨솔 환경에서 동작하는 게임을 완성하는 과정을 단계별로 설명한다.

이 프로젝트는 다음과 같은 목표를 가지고 있다:

  • 컨솔 출력과 좌표 기반 그리드 시스템 구현
  • 사용자의 입력을 실시간으로 처리
  • 문자열 기반의 게임 로직 관리
  • 동적 메모리 할당 및 해제
  • 모듈화된 아키텍처 설계

핵심 기능 분석

타이핑 게임의 주요 기능은 다음과 같다:

  1. 게임 초기화: 화면 크기 설정, 커서 숨기기, 시작 메시지 표시
  2. 문장 생성: 랜덤한 문장을 생성하고 화면에 표시
  3. 입력 감지: 키보드 입력을 실시간으로 받아들임
  4. 진행 상태 추적: 현재 입력 위치, 정확도, 점수 계산
  5. 결과 평가: 문장 완료 여부 판단, 시간 기반 스코어 산출
  6. 재시작 및 종료: 게임 재시작 여부 선택, 리소스 정리

이러한 기능들은 각각 독립된 모듈로 나누어 설계할 수 있으며, 이를 통해 전체 시스템의 유지보수가 용이해진다.


데이터 구조 설계

게임의 핵심은 문자열의 진행 상태를 관리하는 것이다. 이를 위해 다음과 같은 구조체를 정의한다:

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를 활용한 컨솔 제어
  • 입력 처리 및 비동기 처리 이해
  • 모듈화된 프로그래밍 스타일 적용
  • 시간 기반 알고리즘 설계

결론

타이핑 게임은 단순한 테스트 도구가 아니라, 실전에 가까운 소규모 시스템 개발의 좋은 사례이다. 이 과정을 통해 개발자는 단순한 문법을 넘어서, 전체 시스템의 설계와 운영 방식을 경험할 수 있으며, 이는 진정한 프로그래머로 성장하는 데 필수적인 경험이다.

태그: C언어 컨솔 게임 데이터 구조 링크드 리스트 동적 메모리

7월 24일 06:59에 게시됨