(CGP) 9장. 하트 툴 만들기

하트 툴은 하트 게임에 사용되는 가변적인 데이터를 생성하기 위한 프로그램을 말한다.

이때 가변적인 데이터는 메모리와 관련 되므로 주로 링크드리스트를 이용하여 관리한다.

 

셀카유튜브 동영상 강의 주소

 

(1) http://youtu.be/MPDKNMx8l5k

(2) http://youtu.be/vGiANqxpunY

(3) http://youtu.be/2t4INsBKw3c

(4) http://youtu.be/2vtBxjqG4MM

(5) http://youtu.be/LTYHpFMoT7U

(6) http://youtu.be/T8Vpj6psHlQ

(7) http://youtu.be/mfalJxn_4k0

(8) http://youtu.be/Z8OjYc051Tc

 

 

 


9.1 툴 제작 개요

 

게임 제작에서 툴(Tool)은 필수 프로그램이라고 할 수 있다. 물론 툴을 제작하지 않고도 게임을 만들 수 있겠지만 스테이지마다 적용할 데이터가 다른 경우에 매번 코드를 직접 수정하는 것은 상당히 번거로울 뿐만 아니라  실수할 가능성도 높아진다. 특히 게임 제작이 대형화 되고 분업화 되면서 프로그램 팀 외에 기획이나 그래픽 관련 파트에서도 게임에 적용할 툴이 필요하므로 게임에서 있어서 툴 제작은 필수이다.

 

실제 게임에서 툴 제작이 완성되면 게임의 3분의 1 이상을 완성한 것이나 다름없다. 그 만큼 툴 제작은 게임 전체에 중요한 영향을 미친다.

 

툴 제작을 기획하기에 앞서 먼저 살펴봐야 하는 것은 ‘현재 제작하려는 게임이 무엇인가? 에 관한 부분이다. 

그러면 간단한 게임 기획서를 살펴보면서 툴을 제작해 보자.

 

 


9.2 기획

 

■ 스토리

 

하트 바구니에서 떨어지는 하트를 목표 수 만큼 막대기로 받아라.

 

■ 게임 방식

 

하단의 하트 바구니는 j, l키로 좌우 이동을 하며 스테이지마다 정해진 목표 수 만큼 막대기로 받아야 한다. 제한 시간은 없으며 하트가 다 떨어지면 하트 바구니는 자동으로 멈춘다.

 

■ 제한 사항

 

툴에서 저장한 데이터를 읽어 각 스테이지에 적용하고 스테이지마다 막대기의 길이와 하트가 떨어지는 시간간격, 그리고 이동시간을 다르게 적용하여 난이도를 조절한다.

 

■ 기획 화면

 

[그림 9-1] 게임 기획 화면

 

 


9.3 툴 제작 기획 가이드

 

■ 툴에 적용할 데이터 파악

 

툴을 제작하는 이유는 가변적인 데이터를 게임에 적용하기 위해서임을 서두에서 설명하였다.

물론 소스 코드에 데이터를 고정해야 하는 경우도 있겠지만 기획 의도에 따라 수시로 변경되는 데이터는 툴에서 편집할 수 있도록 해 주므로써 각 스테이지는 다양하게 적용될 수 있다. 그러므로 툴을 제작하기 위해서는 게임에서 필요로 하는 가변적인 데이터를 먼저 파악하는 것이 가장 중요하다.

 

■ 메인 메뉴 결정

 

메인 메뉴를 결정하는 것은 툴의 기능을 결정하는 것과 같다. 툴은 대부분 메뉴를 통해서 작업이 실행되므로 메뉴는 툴의 프로그래밍 흐름과 기능을 결정한다.

 

■ 기능 정의 및 처리 방법 설정

 

메인 메뉴가 결정되면 메뉴에 따라 기능을 정의하고 서브 메뉴를 결정한다. 대부분의 툴은 입력, 수정, 삽입, 삭제를 기본 메뉴로 하고 그 외에 특별한 기능이 추가된다.

 

툴을 사용하는 방법이 너무 어려우면 툴에 대한 교육을 따로 해야 하므로 되도록 메뉴 구성과 처리는 사용하기 쉽게 해야 한다. 예를 들어 3D 게임에서 캐릭터 툴 혹은 이펙트 툴 등은 그래픽 디자이너가 사용하기 쉽게 3D MAX(그래픽 툴)와 유사한 툴 구조와 메뉴로 제작한다.

 

 

 

 

[그림 9-2] 게임브리오(Gamebryo) 화면

 

이제까지 살펴본 내용을 기반으로 하트 게임에 어떤 데이터가 적용 되는지를 먼저 분석하고 단계별로 제작해 보자.

 

 


9.4 실행 화면

 

[그림 9-3] 메인 메뉴 화면

 

[그림 9-4] 데이터 입력 화면

 

[그림 9-5] 데이터 삽입 화면

 

[그림 9-6] 데이터 저장 화면

 

[그림 9-7] 입력된 데이터 출력 화면

 

[그림 9-8] 입력된 데이터 수정 화면

 

 


9.5 툴 제작 로드맵

 

우리가 제작하고자 하는 툴은 총 4단계로 나눌 수 있다. 

 

첫 번째는 데이터를 파악하고 메뉴와 기능을 확정하는 단계이다.

이 단계는 프로그램 기획이라고 볼 수 있으며 프로그램의 전체적인 구조와 흐름을 설계하고 간단한 메뉴 형식에 따라 기능이 실행되는지를 확인하는 과정이다.

 

두 번째는 서브 메뉴의 기능을 프로그래밍하는 단계이다.

여기서 실제적인 처리 코드가 작성된다.

 

세 번째는 파일에 관련된 사항이다.

화면으로 입력 받은 데이터는 모두 변수에 저장이 되지만 변수의 데이터는 프로그램 종료와 동시에 메모리에서 사라지게 된다. 그러므로 모든 데이터는 파일로 저장해야 하며 저장된 데이터는 툴과 게임에서 읽을 수 있어야 한다. 

 

게임 프로그래머라고 해서 항상 게임만 프로그래밍하는 것이 아니라 다양한 분야의 프로그래밍 경험은 보다 넓은 자기 영역을 가지게 해 준다. 

 

[STEP 01]

 

[STEP 02]

 

[STEP 03]

 

[STEP 04]

 

[그림 9-9] 툴 제작 로드맵

 

 


9.6 단계별 프로그래밍

 

STEP 01

 

[그림 9-10] 1단계 툴 제작 로드맵

 

■ 데이터 분석

 

하트 게임에 출현하는 캐릭터로는 하트 바구니, 하트, 막대기이며 스테이지마다 하트 바구니의 이동속도와 하트 수, 목표 하트 수, 막대기 길이는 다르게 적용된다.

그러므로 이들 데이터를 입력하고 수정, 삭제, 저장하는 것은 툴의 기본적인 기능이 된다.

여기서 게임에 적용할 가변적인 데이터를 완전히 파악 했다고 해도 기획이 수정되면 툴의 데이터는 언제든지 바뀌게 된다. 이렇듯이 기획이 바뀔 때마다 이전 데이터를 버리고 새 데이터를 입력해야 할 것인가? 그건 아니다. 일반적으로 이런 상황을 대비해서 툴에서는 데이터 버전을 관리하는 기능을 만들어 이전 데이터를 업그레이드 할 수 있게 한다.

 

현재 우리가 제작하는 툴은 데이터의 버전까지 관리할 툴이 아니므로 이 부분을 제외한 내용으로 데이터를 정의하면 다음과 같다.

 

 

① 하트 바구니의 초기 좌표

② Stage별 총 하트 수

③ 목표 하트 수

④ 막대기 길이

⑤ 하트 바구니의 이동 시간 간격

⑥ 하트가 떨어지는 시간 간격

 

[표 9-1] 스테이지 데이터

 

위 데이터는 아래 [소스 9-1]과 같은 구조체로 정의할 수 있으며 이 데이터 구조는 파일을 입력하거나 저장할 때와 데이터를 편집할 때 사용된다.

 

 

typedef struct _STAGE

{        

       int nBasketX;                 // Note: 하트 바구니의 x 좌표

       int nBasketY;               // Note: 하트 바구니의 y 좌표    

       int nHeartCount;          // Note: Stage별 총 하트 수

       int nGoalHeartCount;              // Note: 목표 하트 수

       int nBarLength;           // Note: 막대기 길이

       clock_t BasketMoveTime;      // Note: 하트 바구니의 이동 시간 간격      

       clock_t BasketDownHeartTime; // Note: 하트가 떨어지는 시간 간격

} STAGE;

 

[소스 9-1] 스테이지 데이터 정의

 

■ 메인 메뉴

 

메인 메뉴를 결정할 때는 어떤 데이터가 어떻게 사용될 것인지를 먼저 파악하는 것이 중요하며, 여기서 결정된 데이터에 따라 메뉴를 확정할 수 있다.

기본적인 메인 메뉴로는 입력, 수정, 삭제, 삽입, 출력, 파일 저장, 파일 열기가 있다.

 

 

① 파일 저장

② 파일 열기

③ 데이터 입력

④ 데이터 수정

⑤ 데이터 삽입

⑥ 데이터 삭제

⑦ 데이터 출력

 

[표 9-2] 메인 메뉴

 

■ 메인 메뉴 화면과 메뉴 선택

 

아래 [그림 9-11]과 같은 메인 메뉴 화면은 제작 함수인 gotoxy()와 printf(), scanf()를 이용하면 쉽게 출력할 수 있다.

각 메뉴가 선택되었을 때 서브 메뉴의 실행은 메뉴 선택 번호에 따라 결정되므로 아래 [소스 9-2]와 같이 switch()문을 이용하여 기본적인 구조를 만들 수 있다.

 

[그림 9-11] 메인 메뉴 화면

 

 

while( 1 )

{

    if( 메뉴 선택 번호 == 8 ) // 종료

        break;

 

    switch( 메뉴 선택 번호 )

    {

     case 1 :

              break;

     case 2 :

              break;

     case 3 :

              break;

     case 4 :

              break;

     case 5 :

             break;

      case 6 :

             break;

      case 7 :

             break;

     }

}

 

[소스 9-2] 메인 메뉴의 처리 구조

 

[실습 예제 9-1]

 

위의 [그림 9-11]과 같이 메인 메뉴가 출력되도록 프로그래밍해 보자.

참고적으로 커서를 이동하거나 임의의 위치에 출력하기 위해서는 gotoxy()를 사용해야 하는데  이 부분은 2장 에서 다루었던 내용이다.

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

#include <stdio.h>

#include <windows.h>

 

void gotoxy( int x, int y )

{

     COORD CursorPosition = { x, y };

     SetConsoleCursorPosition( GetStdHandle( STD_OUTPUT_HANDLE ), CursorPosition );

}

 

void MainMenu()

{

     gotoxy( 0, 0 );

     printf( "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n"); 

     printf( "┃                                                        ┃\n");

     printf( "┃            하트 담기 게임 스테이지 디자인 툴           ┃\n");

     printf( "┃                                                        ┃\n");

     printf( "┃                                                        ┃\n");

     printf( "┃       1. 파일 열기                                     ┃\n");

     printf( "┃                                                        ┃\n");

     printf( "┃       2. 파일 저장                                     ┃\n");

     printf( "┃                                                        ┃\n");

     printf( "┃       3. 데이터 입력                                   ┃\n");

     printf( "┃                                                        ┃\n");

     printf( "┃       4. 데이터 삽입                                   ┃\n");

     printf( "┃                                                        ┃\n");

     printf( "┃       5. 데이터 삭제                                   ┃\n");

     printf( "┃                                                        ┃\n");

     printf( "┃       6. 데이타 수정                                   ┃\n");

     printf( "┃                                                        ┃\n");

     printf( "┃       7. 데이터 출력                                   ┃\n");

     printf( "┃                                                        ┃\n");

     printf( "┃       8. 종료                                          ┃\n");

     printf( "┃                                                        ┃\n");

     printf( "┃        메뉴를 선택하세요 [        ]                    ┃\n");

     printf( "┃                                                        ┃\n");

     printf( "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n");

}

 

int Menu()

{

    int nKey;

 

    while( 1 )

    {   

        MainMenu();      

        gotoxy( 32, 21 );

        scanf( "%d", &nKey );

        fflush( stdin ); // Note: 키보드 버퍼를 비운다.

 

        if( nKey >= 1 && nKey <= 8 )

           return nKey;

        else

        {

            gotoxy( 8 ,21 );                     

            printf( "입력이 잘못 되었습니다. 다시 입력해 주세요 !! " );

            Sleep( 1500 );

        }

     }

}

 

int main(void)

{

    int nMenu;

 

    while( 1 )

    {

        nMenu = Menu();

 

        if( nMenu == 8 )

           break;

 

        switch( nMenu )

        {

        case 1 :  // 서브 메뉴

                break;

        case 2 :  // 서브 메뉴

                break;

        case 3 :  // 서브 메뉴

                break;

        case 4 :  // 서브 메뉴

                break;

        case 5 :  // 서브 메뉴

                break;

        case 6 :  // 서브 메뉴

                break;

        case 7 :  // 서브 메뉴

                break;          

         }

    }

    return 0;

}

 

[소스 9-3] 기본 구조

 

48행의 fflush( stdin )은 반복구조 안에 있는 scanf() 때문에 존재한다.

scanf()의 특성을 보면 데이터 입력은 입력값의 끝에 개행 문자가 입력될 때까지이며 개행 문자(‘\n', 10 )는  엔터키가 입력되었을 때 키보드 버퍼에 저장된다. 여기서와 같이 반복문을 통해 매번 scanf()를 실행하게 되면 키보드 버퍼에 남아 있던 개행 문자를 읽어 변수값으로 설정하는 경우가 발생하게 된다.

그래서 엉뚱한 경과가 출력 되므로 반복적으로 scanf()를 사용하는 경우에는 fflush(stdin)을 사용하거나 _getch()로 개행 문자를 읽어 제거해야 한다. 

 

56행의 Sleep() 함수는 윈도우 함수로써 일정 시간 동안 정지하는 역할을 한다.

 


 STEP 02

 

[그림 9-12] 2단계 제작 로드맵

 

■ 데이터 관리

 

툴은 입력, 삽입, 삭제, 수정, 출력과 같은 작업을 반복한다. 입력된 데이터는 일차적으로 메모리에 저장되며 차후에 파일 저장 메뉴를 통해 파일로 저장된다. 여기서 입력과 삽입이 메모리를 할당하는 과정이라면 삭제는 할당된 메모리를 제거하는 과정라 할 수 있다. 수정과 출력은 할당된 메모리에 접근하여 데이터를 읽거나 다시 쓰는 과정이라고 할 수 있다.

이와 같은 과정을 효율적으로 처리하기 위한 방법으로 링크드 리스트(Linked List)가 있으며 일반적으로 메모리 할당과 삭제 등의 작업을 할 때 많이 사용된다. 현재 C 언어에서는 링크드 리스트를 매번 용도에 맞게 제작해야 하지만 C++에서는 STL를 이용하면 쉽게 링크드 리스트를 목적에 맞게 사용할 수 있다. 

러면 먼저 링크드 리스트에 대해서 살펴보고 이것을 툴에 적용해 보자.

 

■ 링크드 리스트(Linked List)

 

링크드 리스트를 구현하기에 앞서 그 개념부터 살펴보도록 하자.

링크드 리스트란 ? 동적인 자료를 관리하기 위한 메모리 관리 기법을 말한다.

같은 데이터형의 동적인 자료는 배열로써 다룰 수 없지만 자기 참조 구조체를 이용하면 동적인 자료를 다룰 수 있으므로 링크드 리스트는 자기 참조 구조체를 필수적으로 사용한다.

 

이제 링크드 리스트를 구현하기 위한 세 가지 개념을 살펴보도록 하자.

 

첫째, 노드(node)이다.

노드는 실제 데이터를 저장하는 구조체를 말한다.

링크드 리스트는 노드와 노드를 연결하여 검색 및 추가를 하며 노드 단위로 생성과 삭제를 한다.

 

둘째, 링크(link)이다.

링크는 한 노드에서 다른 노드로 접근하는 것을 말한다.

즉, 자기 참조 구조체에서 자신과 같은 구조체를 연결할 때 구조체 포인터를 이용하여 구조체와 구조체를 연결한 것을 링크라고 한다.

그래서 링크드 리스트의 기본적인 형태는 자기 참조 구조체가 된다.

 

셋째, head 노드와 tail 노드이다.

링크드 리스트는 필요할 때마다 메모리를 생성하여 연속적으로 연결한다.

이 연속적인 노드 중에서 노드의 시작을 head 노드라고 하며, 마지막 노드를 tail 노드라고 한다. 링크드 리스트에서 head 노드와 tail 노드를 지정하는 이유는 앞서 살펴보았듯이 링크드 리스트는 구조체 포인터를 이용하여 연속적으로 연결되는 구조이며 하나의 노드에서 다른 노드로 접근하는 구조이기 때문이다.

이 연결 구조에서 처음 노드인 head 노드는 모든 노드를 접근할 수 있는 통로가 된다.

 

임의로 생성한 노드의 구조체 포인터 변수에는 기본적으로 이전 메모리에 남아 있던 값들이 저장되어 있다. 만약 tail 노드를 지정하지 않은 상태에서 검색 또는 노드를 이동하게 되면 현재 노드의 구조체 포인터 변수에 엉뚱한 값이 들어 있는지 알 수 없으므로 이 포인터를 이용하여 다른 노드에 접근하려는 순간 프로그램은 다운되고 만다.

그래서 tail 노드를 지정하는 것이며 노드의 검색과 이동 범위는 head 노드와 tail 노드 사이에서만 가능하다.

 

링크드 리스트에는 단순 링크드 리스트(Simple Linked List)와 이중 링크드 리스트( Double Linked List ), 환형 링크드 리스트(Cycle Linked List)가 있으며 이 장에서는 단순 링크드 리스트만 구현해 본다.

단순 링크드 리스트의 형태는 [그림 9-12]와 같다.

 

[그림 9-13] 단순 링크드 리스트(Simple Linked List)

 

단순 링크드 리스트는 노드 연결이 한 개로 이루어져 있다.

이것은 자기 참조 구조체 안에 있는 한 개의 구조체 포인터를 이용하여 다음 노드로 접근하는 것을 의미한다.

이제 단순 링크드 리스트를 자세히 살펴보자.

 

- 링크드 리스트 초기화

 

[그림 9-14] 노드 초기화

 

링크드 리스트에 할당되는 메모리는 차례대로 할당되는 메모리가 아니라 임의의 영역(힙 영역) 중에서 일부분이 할당되는 메모리이다. 임의로 할당된 메모리를 차례대로 읽거나 끝을 인식하기 위해서는 노드의 처음이 되는 head 노드와 노드의 끝이 되는 tail 노드가 필요하다. 그래서 링크드 리스트를 처음 구성할 때는 제일 먼저 [그림 9-14]와 같이 구성하고 그 사이에 여러 노드를 삽입하거나 삭제한다.

 

[그림 9-14]와 같이 head 노드가 tail 노드의 메모리를 기억하여 마치 연결된 것처럼 인식하게 하는 방법은, head 노드의 구조체 포인터에 tail 노드의 메모리 주소를 저장하여 head 노드의 구조체 포인터에서 tail 노드로 접근할 수 있게 하면 된다.

[그림 9-14]을 소스 코드로 나타내면 아래 [소스 9-4]와 같다.

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

typedef struct _NODE       // 자기 참조 구조체

{

    int k;                   // 임의의 변수

    struct _NODE *pNext;   // 구조체 포인터

} NODE;

 

NODE g_Head, g_Tail;

int g_nNodeCount;

  

void Init()

{

    g_Head.pNext = &g_Tail;

    g_Tail.pNext = &g_Tail;//g_Head.pNext->pNext와&g_Tail의 값은 같다.

    g_nNodeCount = 0;        // 링크드 리스트 안에 있는 개수

}

 

[소스 9-4] head 노드와 tail 노드를 링크

 

1행에서 5행까지는 노드를 정의한 자기 참조 구조체이다.

여기서 4행의 pNext 포인터 변수는 struct _NODE 구조체의 메모리 주소를 저장하거나 접근하는 구조체 포인터의 역할을 한다.

 

12행과 13행은 [그림 9-14]의 연결 상태를 만드는 코드이다.

Init() 함수는 링크드 리스트를 초기화하는 함수이므로 링크드 리스트 안에 있는 노드 개수는 14행과 같이 0으로 설정한다. 

 

- 노드 추가

 

[그림 9-15] 노드 추가

 

노드 추가는 [그림 9-15]와 같이 항상 tail 노드 앞에 삽입하므로 먼저 head 노드부터 노드 검색을 하여 tail 이전 노드를 찾아야 한다.

그리고 새로 추가하려는 노드를 tail 이전 노드와 연결하고 추가 노드의 구조체 포인터에는 tail 노드의 메모리 주소를 저장하여 [그림 9-15]와 같이 연결한다.

 

아래 [소스 9-5]의 Add() 함수는 외부에서 생성한 추가 노드의 메모리 주소를 입력받아 [그림 9-15]와 같이 연결하는 함수이다.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

void Add( NODE* pData )

{  

     NODE* pSearch = &g_Head;

  

     while( pSearch->pNext != &g_Tail )

     {

           pSearch = pSearch->pNext;

     }

  

    pSearch->pNext = pData;

    pData->pNext = &g_Tail;

    g_nNodeCount++;

}

 

[소스 9-5] 노드 추가

 

Add() 함수에 노드를 추가할 때 함수 내에서 노드를 생성할 수도 있지만, 노드의 생성은 외부에서 하고 함수 안에서는 [그림 9-15]와 같이 연결만 한다.

 

5행에서부터 8행까지는 다음 노드를 접근하는 pSearch->pNext가 tail 노드의 메모리 주소(&g_Tail)와 같지 않으면 pSearch에 다음 노드의 메모리 주소인 pSearch->pNext를 저장하여 pSearch가 다음 노드를 접근하게 한다. 이로써 pSearch 구조체 포인터는 다음 노드로 이동하게 되며 이 과정은 조건이 거짓일 때까지 반복한다.

 

10행과 11행은 tail 이전 노드의 포인터 변수에 추가 노드의 메모리 주소를 저장하여 연결한다. 그리고 추가 노드의 구조체 포인터에는 tail 노드의 메모리 주소를 저장하여 [그림 9-15]와 같은 구조를 만든다.

[소스 9-5]를 통해 살펴 본 노드 추가 과정을 단계별로 나타내면 다음과 같다.

 

[1단계] 7행과 같이 tail 노드의 앞 노드까지 이동한다.

[그림 9-16] 노드 추가 1단계

 

[2단계] 10행과 같이 tail 이전 노드의 구조체 포인터에 추가될 노드의 메모리 주소를 저장하여 서로 연결한다.

[그림 9-17] 노드 추가 2단계

 

[3단계] 11행과 같이 추가 노드의 구조체 포인터에 tail 노드의 메모리 주소를 저장하여 노드 추가를 완료한다.

[그림 9-18] 노드 추가 3단계

 

- 노드 삽입

 

[그림 9-19] 노드 삽입

 

노드 삽입은, 삽입하려는 위치의 이전 노드와 다음 노드를 [그림 9-19]와 같이 연결하는 것을 말한다. 먼저 삽입하려는 위치의 이전 노드를 검색하고, 이전 노드의 구조체 포인터에 있는 다음 노드의 메모리 주소를 삽입 노드의 구조체 포인터에 저장한다.

그러면 삽입 노드에서 다음 노드를 접근할 수 있게 된다.

 

이전 노드와 삽입 노드의 연결은 이전 노드의 구조체 포인터에 삽입 노드의 메모리 주소를 저장한다. 그러면 이전 노드에서 삽입 노드로 접근할 수 있게 된다.

이와 같은 과정은 [그림 9-19]와 같이 이전 노드에서 삽입 노드로, 삽입 노드에서 다음 노드로 접근할 수 있는 연결 구조가 된다.

 

 

1
2

3

4

5

6

7

8

9

10

11

12

13

14

15

int Insert( NODE* pData, int nIndex )

{                         

     int i;           

     NODE* pSearch = &g_Head;

     if( ( nIndex > g_nNodeCount - 1 ) || ( nIndex < 0 ) )

            return 0;

  

     for( i = 0 ; i < nIndex ; i++ )

          pSearch = pSearch->pNext;

  

     pData->pNext = pSearch->pNext;

     pSearch->pNext = pData;

  

     return 1;

}

 

[소스 9-6] 노드 삽입

 

Insert() 함수의 매개변수인 pData는 삽입 노드의 포인터 변수이며, nIndex는 삽입하려는 위치 인덱스이다.

예를 들어 nIndex의 값이 0이면 처음 위치에 pData 노드를 삽입한다.

 

노드를 위치 인덱스만큼 이동한 후에 삽입하기 위해서는 처음 노드에서부터 위치 인덱스까지 이동해야 한다. 그래서 4행과 같이 head 노드의 주소를 pSearch에 저장한다.

 

5행과 6행은 삽입하고자 하는 위치 인덱스의 허용 범위를 조사하는 부분이다.

삽입하려는 위치 인덱스는 최대 노드의 인덱스(g_nNodeCount-1)보다 작거나 같아야 하며 최소 인덱스는 0부터 시작하므로 0보다는 크거나 같아야 한다.

만약 이 조건이 아니라면 삽입할 위치 인덱스의 범위가 아니므로 Insert() 함수는 실행되지 않아야 한다. 그래서 6행의 return 0; 은 6행 이후의 삽입 코드를 실행하지 못하게 한다.

 

8행과 9행의 반복문은 삽입하려는 위치 인덱스의 이전 노드까지 이동하는 부분이다.

그리고 11행은 삽입 노드와 다음 노드를 연결하며 12행은 삽입 노드와 이전 노드를 연결한다.

 

삽입할 위치 인덱스가 1일 때를 예로 들어 [소스 9-6] 과정을 단계별로 나타내면 다음과 같다.

 

[1단계] 9행과 같이 삽입하려는 위치 인덱스의 이전 노드까지 이동한다.

 

[그림 9-20] 노드 삽입 1단계

 

[2단계] 11행과 같이 삽입 노드의 구조체 포인터에 다음 노드의 메모리 주소를 저장하여 삽입 노드에서 다음 노드를 접근할 수 있게 한다.

 

[그림 9-21] 노드 삽입 2단계

 

[3단계] 12행과 같이 이전 노드의 구조체 포인터에 삽입 노드의 메모리 주소를 저장하여 이전 노드에서 삽입 노드에 접근하게 한다.

 

[그림 9-22] 노드 삽입 3단계

 

- 노드 삭제

 

[그림 9-23] 노드 삭제

 

노드를 삭제하기 위한 검색 방법은 위치 인덱스로 원하는 위치의 노드를 검색하는 방법과  실제 데이터로 검색하는 방법이 있다. 여기에서는 위치 인덱스로 검색하는 방법을 사용한다.

 

노드를 삭제하기 위해서는 [그림 9-23]과 같이 삭제 노드의 이전 노드를 알고 있어야 한다. 그 이유는 이전 노드와 다음 노드를 연결한 후에 삭제 노드를 해제하기 때문이다.

여기서 다음 노드의 메모리 주소는 삭제 노드의 구조체 포인터에 저장되어 있으므로 이 값을 이전 노드의 구조체 포인터에 저장하여 이전 노드와 다음 노드를 연결한다. 이전 노드를 검색하는 방법은 앞에 살펴본 노드 삽입과 노드 추가할 때의 검색 방법과 같다.

아래의 노드 삭제 코드를 보면서 이와 같은 사항을 살펴보자.

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

void Delete( int nIndex )

{

     int i;

     NODE* pSearch = g_Head.pNext;

     NODE* pPreData = &g_Head;

        

     for( i = 0 ; i < nIndex ; i++ )

     {

        pPreData = pSearch;

        pSearch = pSearch->pNext;

     }  

     // Note: 이전 노드와 삭제 노드의 다음 노드를 연결한다.

     pPreData->pNext = pSearch->pNext;

 

     free( pSearch );

     g_nNodeCount--;   

}

 

[소스 9-7] 노드 삭제

 

4행은 head 노드와 tail 노드는 삭제의 대상이 되지 않으므로 head의 다음 노드부터 검색하기 위한 설정이다.

 

삭제하려는 노드를 찾았다면 삭제 노드를 제외한 이전 노드와 삭제 노드의 다음 노드를 서로 연결해야 하므로 반드시 삭제 노드의 이전 노드를 알아야 한다.

5행의 pPreData는 삭제하고자 하는 노드의 이전 노드의 메모리 주소를 저장하는 구조체 포인터이다.

7행부터 11행까지는 삭제 노드의 위치 인덱스를 이용하여 삭제 노드의 이전 노드와 삭제 노드를 찾는 부분이다. 한 예로 위치 인덱스가 1일 때 노드를 삭제하는 과정을 단계별로 나타내면 다음과 같다.

 

[1단계] 4행과 5행의 초기화 상태

 

[그림 9-24] 노드 삭제 1단계

 

[2단계] 9행은 노드를 이동하기 전에 현재 노드의 메모리 주소를 pPreData에 저장하고 10행에서 다음 노드로 이동한다. 삭제 노드로 이동하기 위해 현재의 검색 노드를 pPreData에 임시 저장한다.

 

[그림 9-25] 노드 삭제 2단계

 

[3단계] 마지막 반복을 한 후에 10행의 pSearch는 삭제 노드의 메모리 주소를 저장한다.

 

[그림 9-26] 노드 삭제 3단계

 

[4단계] 13행과 같이 기존 삭제 노드의 연결 관계를 끊고 삭제 노드의 이전 노드와 다음 노드를 연결한다.

 

[그림 9-27] 노드 삭제 4단계

 

[5단계] 15행과 같이 3단계의 삭제 노드인 pSearch를 메모리에서 해제한다.

 

[그림 9-28] 노드 삭제 5단계

 

노드 검색

 

 

노드 검색은 노드 삽입, 추가, 삭제를 하기 위해 필수적으로 해야 하는 부분이다. 노드 검색을 하기 위해 먼저 head 노드의 다음 노드부터 검색하며, 찾고자 하는 위치 인덱스만큼 노드를 이동시킨다. 이 부분은 [소스 9-6]의 노드 삽입 코드인 8~9행과 [소스 9-7]의 노드 삭제 코드인 7 ~ 11행과 일치한다.

 

 

01

02

03

04

05

06

07

08

09

10

11

12

13

NODE* Search( int nIndex )

{

        int i ;

        NODE* pSearch = g_Head.pNext;

        

        // Note: 전체 노드를 검색하기 위해서

        for(  i = 0 ; i < nIndex ; i++ )

        {

            pSearch = pSearch->pNext;

        }

        

        return pSearch;

}

 

[소스 9-8] 노드 검색

 

- 전체 노드 삭제

 

전체 노드 삭제는 노드 초기화와는 반대로 head 노드와 tail 노드 사이의 모든 노드를 해제하는 것을 말한다. 특정 노드를 삭제할 때에는 삭제하기 이전에 검색한 이전 노드와 다음 노드를 연결했었다. 하지만, 전체 노드 삭제는 노드와 사이를 연결할 필요 없이 head 노드 이후부터 tail 노드 이전까지를 차례대로 삭제하면 된다.

그리고 head 노드와 tail 노드 사이에 노드가 전부 해제되면 전체 노드 개수를 0으로 설정한다.

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

void Release()

{

     NODE* pData = g_Head.pNext;

     NODE* pTemp; // 임시 주소 저장 노드

        

     while( pData != &g_Tail )

     {

          pTemp = pData->pNext;

          free( pData );

          pData = pTemp;

     }

        

     g_nNodeCount = 0;

}

 

[소스 9-9] 전체 노드 해제

 

경축! 아무것도 안하여 에스천사게임즈가 새로운 모습으로 재오픈 하였습니다.
어린이용이며, 설치가 필요없는 브라우저 게임입니다.
https://s1004games.com

3행은 전체 노드를 삭제하기 위해서 head 노드의 다음 노드를 삭제 노드로 설정하는 부분이다. 전체 노드를 삭제할 때에도 제외되는 노드는 head 노드와 tail 노드이다.

 

pData를 삭제하면 pData의 다음 노드를 알 수 없으므로 삭제할 때 4행의 pTemp는 pData에 저장된 다음 노드의 메모리 주소를 임시적으로 저장한다.

 

6행부터 11행까지는 tail 노드 이전까지 삭제하는 부분이다.

6행의 반복 과정 중에서 한 개의 노드를 삭제하는 과정을 단계별로 나타내면 다음과 같다.

 

[1단계] 8행을 실행한 부분으로 삭제 노드인 pData의 구조체 포인터에 저장된 메모리 주소를 pTemp에 임시 저장한다.

 

[그림 9-29] 전체 노드 삭제 1단계

 

[2단계] 9행에서 pData의 메모리를 해제하면 아래와 같은 구조가 된다.

 

[그림 9-30] 전체 노드 삭제 2단계

 

[3단계] 10행에서 삭제한 노드의 다음 노드를 삭제하기 위해 임시 저장한 노드의 메모리 주소(pTemp)를 pData에 저장하고 tail 노드가 나올 때까지 1단계에서부터 3단계까지를 반복한다.

 

[그림 9-31] 전체 노드 삭제 3단계

 

단순 링크드 리스트를 구성하는 함수를 이용하여 int형 데이터를 입력, 삽입, 삭제, 수정, 출력하는 프로그램을 작성해 보면 다음과 같다.

이미 링크드 리스트의 함수는 앞에서 소개했으므로 전체 소스에서 생략하고 main() 함수 부분만 살펴보면 다음과 같다.

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

#include <stdio.h>

#include <malloc.h>

 

typedef struct _NODE

{

        int k;

       struct _NODE *pNext;

} NODE;

 

// Note:  링크드 리스트 함수 생략

 

int main(void)

{

    int i;

    NODE* pNode;

 

    Init();   // Note:  링크드 리스트 초기화

 

    // Note: 노드 생성 및 초기화

    for( i = 0 ; i < 3 ; i++ )

    {

        pNode = (NODE*)malloc( sizeof( NODE) );

        pNode->k = i;

        Add( pNode );

    }

    printf( "%d %d %d \n", Search( 0 )->k, Search( 1 )->k, Search( 2 )->k );

        

    // Note: 삽입할 노드 생성

    pNode = (NODE*)malloc( sizeof( NODE) );

    pNode->k = -1;

    Insert(  pNode, 1 );

    printf( "%d %d %d %d\n", Search( 0 )->k, Search( 1 )->k, Search( 2 )->k ,

                               Search( 3 )->k);

    // Note: 노드 삭제

    Delete( 1 );

    printf( "%d %d %d \n", Search( 0 )->k, Search( 1 )->k, Search( 2 )->k );

 

    // Note: 링크드 리스트의 모든 노드 삭제

    Release();

    return 0;

}

 

[소스 9-10] 링크드 리스트

 

[그림 9-32] 링크드 리스트 출력

 

26행에서 Search() 함수의 리턴값 데이터형은 NODE *형 이므로 Search(0)->k,

Search(1)->k등과 같은 표현이 가능하다.

Search(0)->k를 다른 형태로 표현하면 다음과 같다.

 

 

형식 1

형식 2

Search(0)->k

pNode = Search(0);

      pNode->k;

 

[표 9-3] 같은 의미의 다른 형식

 

■ 데이터 다루기

 

위에서 학습한 링크드 리스트를 이용하여 입력, 삽입, 삭제, 수정, 출력을 구현해 보자.

 

- 입력

 

입력을 받기 위해서는 반드시 메모리가 있어야 하며 입력될 메모리 구조는 [소스 9-1]에서 이미 정의했었다. 이 STAGE 구조체에 데이터를 저장하기 위해서는 먼저 메모리를 할당하고 화면으로부터 입력을 받아 STAGE 구조체 변수에 값을 대입하면 된다. 다음 [그림 9-33]과 같이 화면으로 입력을 받는 출력 함수는 다음과 같이 제작한다.

[그림 9-33] 데이터 입력 화면

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

void DataInput()

{

     system( "cls" );

     gotoxy( 0, 0 );

     printf( "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n"); 

     printf( "┃                                                        ┃\n");

     printf( "┃                                                        ┃\n");

     printf( "┃                                                        ┃\n");

     printf( "┃   하트 바구니의 이동 시간 간격 [         ]             ┃\n");

     printf( "┃                                                        ┃\n");

     printf( "┃   하트가 떨어지는 시간 간격    [         ]             ┃\n");

     printf( "┃                                                        ┃\n");

     printf( "┃   초기  바구니 X 좌표          [         ]             ┃\n");

     printf( "┃                                                        ┃\n");

     printf( "┃   초기 바구니 Y 좌표           [         ]             ┃\n");

     printf( "┃                                                        ┃\n");

     printf( "┃   Stage 총 하트 수             [         ]             ┃\n");

     printf( "┃                                                        ┃\n");

     printf( "┃   Stage 목표 하트수            [         ]             ┃\n");

     printf( "┃                                                        ┃\n");

     printf( "┃   막대 길이                    [         ]             ┃\n");

     printf( "┃                                                        ┃\n");

     printf( "┃                                                        ┃\n");

     printf( "┃                                                        ┃\n");

     printf( "┃                                                        ┃\n");

     printf( "┃                                                        ┃\n"); 

     printf( "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n");

}

 

[소스 9-11] 입력 화면

 

[실습 예제 9-2]

 

스테이지에 관련된 데이터를 입력받기 위해서 [소스 9-11]과 같이 출력 함수를 만들었다.

이제 이 함수를 이용하여 [실습 예제 9-1]의 [소스 9-3] ‘case 3 :’ 부분에 스테이지 정보를 입력받아 링크드 리스트에 추가하는 코드를 작성해 보자.

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

case 3 : // Note: 데이타 입력

        DataInput();     

        gotoxy( 23, 2 );

        printf( "%d Stage", g_nNodeCount + 1);

        pNode = (NODE*)malloc( sizeof( NODE ) );

        gotoxy( 38, 4 );

        scanf( "%d", &pNode->sStage.BasketMoveTime );

        fflush( stdin );

        gotoxy( 38, 6 );

        scanf( "%d", &pNode->sStage.BasketDownHeartTime );

        fflush( stdin );

        gotoxy( 38, 8 );

        scanf( "%d", &pNode->sStage.nBasketX );

        fflush( stdin );

        gotoxy( 38, 10 );

        scanf( "%d", &pNode->sStage.nBasketY );

        fflush( stdin );

        gotoxy( 38, 12 );

        scanf( "%d", &pNode->sStage.nHeartCount );

        fflush( stdin );

        gotoxy( 38, 14 );

        scanf( "%d", &pNode->sStage.nGoalHeartCount );

        fflush( stdin );

        gotoxy( 38, 16 );

        scanf( "%d", &pNode->sStage.nBarLength );

        fflush( stdin );                 

        Add( pNode );    // Note: 링크리스트에 추가

        break;

 

[소스 9-12] 스테이지 데이터 추가

 

5행은 입력 받기 위한 메모리를 할당하는 부분이며 6행부터 26행까지는 scanf()를 이용하여 노드 구조체의 멤버 변수에 데이터를 입력 받는 부분이다.

 

27행은 할당한 메모리로 데이터를 입력 받은 후에 노드를 링크리스트에 추가하는 부분이다.

 

- 삽입

 

데이터를 삽입하고자 할 때에는 몇 번째에 삽입을 할 것인지에 대한 위치 인덱스를 먼저 입력 받아야 링크드 리스트 안에서 노드를 찾을 수 있다.

그래서 삽입할 데이터를 입력 받기 전에 먼저 아래 [그림 9-34]와 같이 위치 인덱스를 입력받는다.

 

[그림 9-34] 삽입 노드의 위치 인덱스를 입력 받는 화면

 

위의 출력 화면은 삽입뿐만 아니라 삭제, 수정할 때에도 동일하게 사용하는 화면이므로 아래 IndexMenu() 함수의 매개변수에 ‘삽입’, ‘삭제’ 또는 ‘수정’과 같은 문자열을 입력하여  용도에 따라 같은 형식의 메시지 박스를 출력한다.

 

 

1

2

3

4

5

6

7

8

9

10

11

12

void IndexMenu( char* str )

{

     system( "cls" );

     gotoxy( 0, 7 );

     printf( "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n");

     printf( "┃                                                        ┃\n");

     printf( "┃                                                        ┃\n");

     printf( "┃     %s할 인덱스를 입력해주세요  [       ]             ┃\n", str );

     printf( "┃                                                        ┃\n");

     printf( "┃                                                        ┃\n");

     printf( "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n");

}

 

[소스 9-13] 메시지 박스

 

노드 삽입은 먼저 메모리를 할당하고 삽입하고자 하는 위치 인덱스를 [그림 9-34]와 같이 입력 받아 노드를 삽입한다. 삽입 코드는 [실습 예제 9-1]의 [소스 9-3] ‘case 4 :’  부분에 추가하면 된다.

 

[그림 9-35] 삽입을 위한 데이터 입력 화면

 

[실습 예제 9-3]

 

삽입하려는 위치 인덱스를 [그림 9-34]와 같이 입력한 후에 [그림 9-35]와 같이 데이터가 입력되도록 [실습 예제 9-1]의 [소스 9-3] ‘case 4 :’ 부분에 코드를 작성해 보자.

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

case 4 : // Note: 데이타 삽입

        IndexMenu( "삽입" );

        gotoxy( 41, 10 );

        scanf( "%d", &nIndex );

        DataInput();

        pNode = (NODE*)malloc( sizeof( NODE ) );

        gotoxy( 38, 4 );

        scanf( "%d", &pNode->sStage.BasketMoveTime );

        fflush( stdin );

        gotoxy( 38, 6 );

        scanf( "%d", &pNode->sStage.BasketDownHeartTime );

        fflush( stdin );

        gotoxy( 38, 8 );

        scanf( "%d", &pNode->sStage.nBasketX );

        fflush( stdin );

        gotoxy( 38, 10 );

        scanf( "%d", &pNode->sStage.nBasketY );

        fflush( stdin );

        gotoxy( 38, 12 );

        scanf( "%d", &pNode->sStage.nHeartCount );

        fflush( stdin );

        gotoxy( 38, 14 );

        scanf( "%d", &pNode->sStage.nGoalHeartCount );

        fflush( stdin );

        gotoxy( 38, 16 );

        scanf( "%d", &pNode->sStage.nBarLength );

        fflush( stdin );                 

        Insert( pNode, nIndex );  // Note: 노드 데이터 삽입  

        break;

 

[소스 9-14] 스테이지 데이터 삽입

 

- 삭제

 

삭제를 할 때에도 삭제하고자 하는 위치 인덱스를 아래 [그림 9-36]과 같이 입력 받는다.

 

[그림 9-36] 삭제할 위치 인덱스를 입력 받는 화면

 

[실습 예제 9-4]

 

위의 [그림 9-36]과 같이 삭제하기 위한 위치 인덱스를 입력 받고 링크드 리스트 안에 있는 노드를 삭제하는 코드를 [실습 예제 9-1]의 [소스 9-3] ‘case 5 :’ 부분에 작성하여 보자.

 

 

1

2

3

4

5

6

case 5 : // Note: 데이타 삭제

        IndexMenu( "삭제" );

        gotoxy( 41 , 10);

        scanf( "%d", &nIndex );

        Delete( nIndex ); 

        break;

 

[소스 9-15] 스테이지 데이터 삭제

 

- 수정

 

수정도 삭제와 동일하게 수정하려는 위치 인덱스를 먼저 입력 받아야 한다.

 

[그림 9-37] 수정할 위치 인덱스를 입력 받는 화면

 

수정 항목의 데이터를 수정하는 방법으로는 먼저 위치 인덱스를 입력받고 해당되는 데이터를 노드에 찾아 [그림 9-38]과 같이 출력한 후에 새로이 입력 받으면 된다.

이때 항목마다 수정 내용이 있으면 새로이 데이터를 입력하고 수정할 필요가 없는 항목은 엔터키를 입력하여 다음 항목으로 이동하도록 하면 원활한 수정 처리가 된다.

 

[그림 9-38] 수정 항목 화면

 

[실습 예제 9-5]

 

위의 [그림 9-37]과 같이 수정할 인덱스를 입력 받은 후에 [그림 9-38]과 같이 화면에서  수정할 수 있도록 [실습 예제 9-1]의 [소스 9-3] ‘case 6 :‘ 부분에 코드를 작성해 보자.

그리고 앞서 설명한 것 같이 수정 항목에서 엔터키 입력이 들어오면 다음 항목으로 커서가 이동되도록 프로그래밍해 보자.

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

case 6 : // Note: 데이타 수정

        IndexMenu("수정" );

        gotoxy( 41 , 10);

        scanf( "%d", &nIndex ); fflush( stdin );

        pNode = Search( nIndex );

        

        //  검색한 스테이지 정보를 임시 변수에 저장한다.

        //  pNode에 직접 값을 대입하지 않는 것은 취소가 발생할 수도 있기 때문이다.

        sTempStage = pNode->sStage; 

        

        DataInput();

        gotoxy( 38, 4 );

        printf( "%d", pNode->sStage.BasketMoveTime );

        gotoxy( 38, 6 );

        printf( "%d", pNode->sStage.BasketDownHeartTime );                          

        gotoxy( 38, 8 );

        printf( "%d", pNode->sStage.nBasketX );                            

        gotoxy( 38, 10 );

        printf( "%d", pNode->sStage.nBasketY );                                    

        gotoxy( 38, 12 );

        printf( "%d", pNode->sStage.nHeartCount );                                  

        gotoxy( 38, 14 );

        printf( "%d", pNode->sStage.nGoalHeartCount );                                      

        gotoxy( 38, 16 );

        printf( "%d", pNode->sStage.nBarLength );

                        

        gotoxy( 38, 4 );

        gets( strData );

        if( strlen( strData ) > 0 )

        {                               

           nTemp = atoi( strData );

           sTempStage.BasketMoveTime = nTemp;        

        }

        gotoxy( 38, 6 );

        gets( strData );

        if( strlen( strData ) > 0 )

        {                               

           nTemp = atoi( strData );

           sTempStage.BasketDownHeartTime = nTemp;

        }

        gotoxy( 38, 8 );

        gets( strData );

        if( strlen( strData ) > 0 )

        {                               

           nTemp = atoi( strData );

           sTempStage.nBasketX = nTemp;

         }

        gotoxy( 38, 10 );

        gets( strData );

        if( strlen( strData22 ) > 0 )

        {                               

           nTemp = atoi( strData );

           sTempStage.nBasketY = nTemp;      

        }

        gotoxy( 38, 12 );

        gets( strData );

        if( strlen( strData ) > 0 )

        {                               

           nTemp = atoi( strData );

           sTempStage.nHeartCount = nTemp;   

        }

        gotoxy( 38, 14 );

        gets( strData );

        if( strlen( strData ) > 0 )

        {                               

           nTemp = atoi( strData );

           sTempStage.nGoalHeartCount = nTemp;       

        }

        gotoxy( 38, 16 );

        gets( strData );

        if( strlen( strData ) > 0 )

        {                               

           nTemp = atoi( strData );

           sTempStage.nBarLength = nTemp;  

        }

 

        gotoxy( 18, 18 );

        printf( "수정된 내용을 적용할까요? (y/n)  " );

        scanf( "%c", &cYN );

        if( cYN == 'y' )

        {

           pNode->sStage = sTempStage;

           OKMenu();

        }

        break;

 

[소스 9-16] 스테이지 데이터 수정

 

수정할 노드는 5행과 같이 Search() 함수를 통해 링크드 리스트 안에서 찾을 수 있다.

9행과 같이 찾은 노드의 스테이지 변수를 임시 저장 변수인 sTempStage에 복사하여 수정 처리를 하는 이유는 81행의 질문에 따라 수정한 내용이 취소가 될 수 있기 때문이다.

즉 원본 데이터에 영향을 주지 않기 위해서이며 최종적으로 80행과 같이 결정되면 원본 스테이지 변수에 수정된 sTempStage의 내용을 복사함으로써 데이터 수정은 완료된다.

 

30행의 gets() 함수는 문자열을 입력 받는 함수이다. 입력 받은 문자열을 30행과 같이 조사하여 입력된 내용이 있으면 atoi()함수를 통해 정수로 변환하여 수정하게 된다.

 

- 출력

 

출력 메뉴는 현재 링크드 리스트 안에 있는 모든 노드의 내용을 아래 [그림 9-39]와 같이  출력한다. 출력은 사용자가 데이터를 확인 또는 검색해 보는 단계이므로 전체 데이터를 한눈에 볼 수 있도록 하는 것이 좋다.

 

 

[그림 9-39] 출력 화면

 

[실습 예제 9-6]

 

링크드 리스트 안에 있는 모든 노드의 데이터가 위의 [그림 9-39]와 같이 출력될 수 있도록 [실습 예제 9-1]의 [소스 9-3] 'case 7 :‘ 부분을 프로그래밍해 보자.

 

 


STEP 03

[그림 9-40] 3단계 제작 로드맵

 

모든 데이터는 파일로 저장되며 메모리로 업로드된다. ‘파일 열기’를 다루기 전에 ‘파일 저장’ 부분을 먼저 프로그래밍하는 이유는 저장하고자 하는 데이터가 정상적으로 저장되었는지를 쉽게 확인할 수 있기 때문이다.

이를 위해 저장할 파일 형식은 텍스트(text) 파일 형태로 하고 메모장과 같은 일반 에디터(editor) 프로그램에서 확인해 보자.

 

■ 파일 저장

 

데이터를 텍스트 파일로 저장하기 위해서는 [표 9-4]와 같은 함수를 기본적으로 사용할 수 있어야 하며 함수 사용 방법은 이미 2장 함수에서 다루었던 내용이다.

 

 

함수명

설명

fopen()

파일 읽기, 쓰기, 추가 모드로 연다.

fclose()

파일을 닫는다.

fprintf()

형식에 따라 데이터를 파일로 저장한다.

fscanf()

파일로부터 형식에 따라 데이터를 읽는다.

 

[표 9-4] 파일 입출력 함수

 

파일에 저장하기 위한 데이터는 1단계의 [소스 9-1]에서 구조체로 이미 정의했으며 이 데이터들은 링크드 리스트의 노드에 저장된다.

 

파일로 저장되는 데이터는 구조체 단위로 저장되므로 저장하려는 구조체의 개수를 알면 파일 입출력할 때 쉽게 그 개수만큼 구조체를 생성하여 읽어 내거나 저장할 수 있다.

링크드 리스트에는 현재 노드 개수를 알 수 있는 변수가 있으므로 for 반복문을 이용하여 구조체 단위로 데이터를 읽거나 저장할 수 있다.

 

[그림 9-41] 저장할 파일명을 입력 받는 화면

 

[실습 예제 9-7]

 

[그림 9-41]과 같이 파일명을 입력 받고 링크드 리스트의 노드 데이터를 저장하는 프로그램을 [실습 예제 9-1]의 [소스 9-3] ‘case 2 :’ 부분에 작성해 보자. 그리고 파일의 확장자명은 txt로 하여 메모장과 같은 일반 에디터 프로그램에서 확인할 수 있도록 하자.

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

case 2 :// Note: 파일 저장                                

        FileSaveMenu();

        gotoxy( 12, 5 );

        scanf( "%s", strFileName );

        fflush( stdin );

        if( fp != NULL )

           fclose( fp );

 

        fp = fopen( strFileName, "w" );                            

        fprintf( fp, "%d\n", g_nNodeCount );

        for( i = 0; i < g_nNodeCount ; i++ )

        {

             pNode = Search( i );

             fprintf( fp,  "%d %d %d %d %d %d %d\n", pNode->sStage.BasketMoveTime,

                                          pNode->sStage.nBasketDownHeartTime,

                                                  pNode->sStage.nBasketX,

                                                  pNode->sStage.nBasketY,

                                                  pNode->sStage.nHeartCount,

                                                  pNode->sStage.nGoalHeartCount,

                                                  pNode->sStage.nBarLength );

        }

        fclose( fp );

        fp = NULL; // 파일을 종료 했음을 알림

        break;

 

[소스 9-17] 스테이지 데이터 저장

 

10행과 같이 링크드 리스트의 노드 개수를 먼저 저장하는 이유는 게임 또는 툴에서 저장된 데이터를 읽고자 할 때 얼마만큼의 데이터를 읽어야 하는지를 알 수 있기 때문이다.

이와 같이 파일에 저장하거나 읽고자하는 데이터의 개수를 알면 몇 번 반복을 해야 하는지 알 수 있으므로 메모리를 미리 할당하거나 11행과 같이 반복문을 이용하기가 쉽다.

 

■ 파일 읽기

 

저장된 파일을 읽는 방법은 저장 방법의 반대로 하면 된다. 주의할 것은 저장된 파일을 읽는 경우에는 반드시 링크드 리스트를 초기화해야 한다는 점이다. 여기서 초기화란 기존에 할당된 메모리가 있다면 전부 삭제해야 하며 head 노드와 tail 노드의 연결, 노드 개수를 0으로 설정하는 것을 말한다.

 

[그림 9-42] 읽을 파일명을 입력 받는 화면

 

[실습 예제 9-8]

 

위의 [그림 9-42]와 같이 읽을 파일명을 입력 받아 링크드 리스트에 데이터를 입력하는 프로그램을 작성해 보자. 이 부분에 대한 코드는 [실습 예제 9-1]의 [소스 9-3] ‘case 1 : ’ 부분에 작성하면 된다.

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

case 1 : // Note: 파일 읽기

        Release(); // Note: 할당된 노드 전체 삭제

        Init();     // Note: 링크리스트 초기화

        FileInputMenu();

        gotoxy( 12, 5 );

        scanf( "%s", strFileName );

        fflush( stdin );

        if( fp != NULL )

            fclose( fp );

 

        fp = fopen( strFileName, "r" );

        fscanf( fp, "%d\n", &nStageCount );

 

        for( i = 0 ; i < nStageCount ; i++ )

        {

           pNode = (NODE*)malloc( sizeof( NODE ));

           fscanf( fp, "%d %d %d %d %d %d %d\n", &pNode->sStage.BasketMoveTime,

                        &pNode->sStage.BasketDownHeartTime,

                        &pNode->sStage.nBasketX, &pNode->sStage.nBasketY,

                        &pNode->sStage.nHeartCount,

                        &pNode->sStage.nGoalHeartCount,

                        &pNode->sStage.nBarLength );

           Add( pNode );

        }

        fclose( fp );

        fp = NULL;

        break;

 

[소스 9-18] 스테이지 데이터 읽기

 

데이터를 읽기 전에 2행과 같이 링크드 리스트의 모든 노드를 메모리에서 삭제하고 3행과 같이 링크드 리스트를 초기화한 후에 데이터를 입력 받아야 한다.

 

저장된 파일의 제일 처음에 있는 데이터는 저장된 링크드 리스트의 노드 개수이다.

그러므로 12행과 같이 파일로부터 노드의 개수를 읽어와 그 수만큼 14행에서부터 24행까지를 반복하며 데이터를 읽어 링크드 리스트에 입력한다.

 

여기까지 툴에 대한 부분을 살펴보았고 대부분의 코드는 [실습 예제 9-1]에 연결되어 완성되므로 이 예제의 기본 구조를 기억하기 바란다. 앞으로 제작하는 대부분의 툴은 이와 같은 기본 구조를 가지게 된다.

 

여기까지 살펴본 내용으로 전체 툴을 완성하고 다음 장의 실제 게임에 툴 데이터를 적용하여 보자.

 

 

 

좋은하루강의가 도움이 되셨습니까? 손가락 꾸욱 눌러주는 센스 ~~ 

 

[출처] https://nowcampus.tistory.com/entry/1%EC%9E%A5-1?category=655340

 

 

본 웹사이트는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.
번호 제목 글쓴이 날짜 조회 수
108 [Xamarin] Xamarin.Forms. Android 실행/ 디버깅시에 에뮬리이터 배포오류 Why am I getting this error in Xamarin.Forms using Visual Studio? file 졸리운_곰 2021.12.01 22
107 [Xamarin] Visual Studio 2019 를 설치하고 Xmarin.forms 빌드시 에러 : I am just download and start Visual Studio (Xamarin Project). But there is an ERROR NU1101. file 졸리운_곰 2021.12.01 53
106 [게임개발] How to Create Smarter NPCs in Games file 졸리운_곰 2021.08.31 23
105 (CGP)16장. 탱크 게임 file 졸리운_곰 2021.06.28 802
104 (CGP) 15장. 탱크 맵툴 만들기 file 졸리운_곰 2021.06.28 225
103 (CGP) 14장 Sogo 게임 file 졸리운_곰 2021.06.28 21
102 (CGP)13장. 패턴 뷰어 file 졸리운_곰 2021.06.28 19
101 (CGP) 12장 Snake 게임 file 졸리운_곰 2021.06.28 23
100 (CGP) 11장 Snake 게임 툴 만들기 file 졸리운_곰 2021.06.28 51
99 (CGP) 10장. 하트담기 게임 file 졸리운_곰 2021.06.28 25
» (CGP) 9장. 하트 툴 만들기 file 졸리운_곰 2021.06.28 24
97 (CGP) 8장. 벽돌깨기 게임 file 졸리운_곰 2021.06.28 48
96 (CGP)7장. 짝 맞추기 게임 file 졸리운_곰 2021.06.28 131
95 (CGP) 6장. 두더지 잡기 게임 file 졸리운_곰 2021.06.28 113
94 (CGP) 5장. 슛골인 게임 file 졸리운_곰 2021.06.27 151
93 (CGP) 4장. 사운드 file 졸리운_곰 2021.06.27 78
92 (CGP) 3장. 게임의 기본 구조 file 졸리운_곰 2021.06.27 126
91 (CGP) 2장. 함수 file 졸리운_곰 2021.06.27 36
90 (CGP) 1장. C언어 file 졸리운_곰 2021.06.27 60
89 (CGP) 0장. C를 이용한 게임프로그래밍 강좌를 시작하기 전에 file 졸리운_곰 2021.06.27 173
대표 김성준 주소 : 경기 용인 분당수지 U타워 등록번호 : 142-07-27414
통신판매업 신고 : 제2012-용인수지-0185호 출판업 신고 : 수지구청 제 123호 개인정보보호최고책임자 : 김성준 sjkim70@stechstar.com
대표전화 : 010-4589-2193 [fax] 02-6280-1294 COPYRIGHT(C) stechstar.com ALL RIGHTS RESERVED