2012년 12월 31일 월요일

입력장치의 출력기능?

버티고 버티다 이제야 겨우 옆에 두게 된 스마트폰을 쓰다 보니,
신기하고 편리한 장점들을 비집고 불편한 점이 고개를 내밀기 시작합니다.

그 가운데에서 (아마도 많은 분들이 이미 오래전부터 수없이 지적했으리라 생각되는)
문제점은 입력의 어려움이 아닐까 싶습니다.

가상 키보드를 통한 문자의 입력과,
게임 등에서 조이스틱/조이패드를 흉내낸 가상패드에 의한 입력이 그것인데요...



캡콤의 레지던트이블 (좌측 하단이 가상 조이패드)


이러한 가상의 입력장치들이 문제가 되는 원인을 생각하다 보니,
스마트폰 혹은 터치스크린의 크기 제약에 따른 문제점이 아니라,
출력기능의 부족(?)이 원인이 아닌가 하는 생각이 들었습니다.


좀 헷갈릴 수도 있는데, 우선 정보의 전달 방향에 따라 다음과 같이 정의를 해 보겠습니다.
[입력]은 (사람 -> 장치)
[출력]은 (장치 -> 사람)

스마트폰의 가상키보드는 [입력] 기능에 [출력] 기능이 보태어져 있습니다.
위의 사진에서 보듯, 눌린 키가 어떤 것인지 시각적으로 보여주는 것이 [출력] 기능입니다.
(메시지 입력란에 출력된 문자는 가상키패드의 출력이 아니라 메시지앱의 처리 결과이므로 별개의 성격입니다.)

게임의 가상조이패드를 보면 문제가 더 명확해지는데, 이 조이패드의 [출력] 기능은 눌려진 방향으로 패드 중앙의 원이 움직이는 것인데, 통상 두툼한 엄지손가락에 가려져 잘 보이지 않아 출력 기능이 매우 취약한 편입니다.


그렇다면 원래 입력장치인 이런 장치들의 모범적인 [출력] 기능은 무엇일까요?
바로 "촉각"입니다.
데스크탑/노트북 컴퓨터에서 사용하는 키보드는 강도와 특성에 차이는 있지만 "키감"이라는 느낌을 [출력]하고 있습니다. (장치에 따라서는 소리도 한 몫 할 수 있습니다.)

조이스틱 혹은 조이패드는 이 촉각의 출력이 매우 명확하기에 사용자가 신뢰하는 장치가 되었는지도 모르겠습니다.


스마트폰 이전에는 고유한 [입력] 장치로만 인식되었던 키보드와 조이스틱/조이패드가, 사실은 그 위치를 유지할 수 있었던 것이 그들의 [출력] 기능 때문이 아니었나 새삼 돌아보게 됩니다.

2012년 12월 14일 금요일

GTK+ on MS-Windows

MS-Windows에서 GUI를 작성하는 방법은 Win32 SDK를 근본으로 합니다.

Win32 SDK에서 파생된 것으로 Microsoft의 MFC가 있으며, 그 외에도 여러가지 GUI 라이브러리들이 존재하는데, Linux에서 시작된 GTK+라는 라이브러리를 이용하는 방법을 알아보고자 합니다.

GTK+는 Gimp Tool Kit의 약자로 Gimp라는 유명한 공개 Graphic Software가 있는데,
아마도 이 software를 개발하면서 만든 라이브러리를 범용으로 확장한 것이 아닌가 추측이 됩니다.

Gimp의 특징은 Linux / MS-Windows / Mac 등에서 모두 사용이 가능하다는 점과 C 언어 인터페이스를 제공하므로 C와 C++에서 모두 사용이 가능하는 장점이 있습니다.

홈페이지는 http://www.gtk.org 입니다.

MS-Windows용 라이브러리는 다음에서 받을 수 있습니다.
http://www.gtk.org/download/win32.php

GTK+는 여러가지 라이브러리가 모여서 하나의 패키지를 이루고 있습니다.
이 모든 것을 별도로 받을 수도 있으나 번거로우므로 all-in-one bundle을 제공합니다.
http://ftp.gnome.org/pub/gnome/binaries/win32/gtk+/2.24/gtk+-bundle_2.24.10-20120208_win32.zip

다운로드 받아서 적당한 폴더에 압축을 풀면 다음과 같은 형태의 파일과 폴더가 나옵니다.
GTK+ 압축을 푼 결과

저기에 보이는 README 파일을 읽어 보면 해야할 일이 간단하게 나옵니다.

1) bin 폴더를 PATH에 추가한다.
2) "pkg-config --cflags gtk+-2.0"를 수행했을 때 적절한 문자열이 나오는지 확인한다.
3) "gtk-demo"가 제대로 수행되는지 확인한다.
4) MS-Windows theme engine을  사용하려면, etc/gtk-2.0/gtkrc에
    gtk-theme-name = "MS-Windows"
   라는 줄을 넣어 준다.

2번의 pkg-config는 여러가지 옵션이 있는데, 자세한 사용법은 "pkg-config --help"로 찾아 볼 수 있습니다.
몇가지 사용 결과는 다음과 같습니다.
pkg-config 사용 예

3번의 gtk-demo를 수행하면 다음과 같이 실행이 됩니다.

여러가지 GUI component에 대한 예제를 볼 수 있으며 그 소스도 참조가 가능합니다.

그런데 이 데모를 수행하면서 다음과 같은 경고/에러 메시지가 나오는군요.
라이브러리 소스를 따로 받아서 확인해야 할 듯 합니다.


4번째의 Theme Engine이라는 것은 효과가 있는지 없는지 아직은 잘 모르겠습니다.


이제는 실제 코딩하여 빌드하는 것이 가능한지 확인해 보겠습니다.

Code:Blocks에서 Project 종류로 GTK+를 선택하면 몇가지 설정 중, GTK+의 경로를 물어보는 과정이 있습니다.
그리고 GTK+를 처음 사용하는 것일 경우에는 다음과 같이 GTK+에 대한 Global Variable 설정을 해 주어야 합니다.
GTK+에 대한 설정 화면
여기에 base, include, lib를 지정해 주고, cflags는 "pkg-config --cflags gtk+-2.0"에서 나온 문자열을 넣어 줍니다. (cflags는 효과가 있는지 의심스럽습니다.)

이렇게 만들어진 기본 소스를 빌드해 보면 헤더파일을 찾지 못해서 에러가 수없이 나오게 됩니다.
그 원인을 찾아 보니 프로젝트가 만들어지면서 GTK와 관련된 헤더파일의 경로가 자동으로 추가되는데 몇개의 경로는 추가가 안되기도 하고 쓸데없는 경로가 추가되는 등, 좀 어처구니 없이 만들어집니다.
처음에 만들어졌을 때, 추가된 헤더파일의 경로

추가된 헤더파일의 경로를 수정한 결과
아마도 이것은 Code:Blocks의 오류인 듯 싶습니다.

앞서의 Global Variable 설정에서 cflags에 있는 대로 여기에 경로가 추가되었다면 문제가 없었을 것인데, Code:Blocks 내부에서 임의로 경로를 추가하는 게 아닌지 의심스럽습니다.

암튼, 이렇게 수정하고 나면 문제 없이 빌드되고 다음과 같이 실행도 됩니다.

2012년 12월 3일 월요일

미로 생성



#ifndef MAZE_GEN_TREE_H

#ifndef TRUE
#define TRUE    1
#endif
#ifndef FALSE
#define FALSE   0
#endif

/* 미로의 크기, 폭과 높이를 가진 직사각형으로 제한 */
#define MAZE_WIDTH      20
#define MAZE_HEIGHT     20

/* 각 방이 가질 수 있는 방향에 대한 정의. 시각적으로 상하좌우를 갖는다고 가정함. */
typedef enum {
    MAZE_DIR_UP = 0,
    MAZE_DIR_DOWN,
    MAZE_DIR_LEFT,
    MAZE_DIR_RIGHT,
    MAZE_DIR_MAX
} MAZE_DIR;

/* 각 방을 중심으로 4가지 방향에 존재할 수 있는 종류. 미결정, 벽, 부모(들어오는 방향 표시), 자식 */
typedef enum {
    MS_UNDEF = 0,
    MS_WALL,
    MS_PARENT,
    MS_CHILD
} MAZE_STATUS;



/* 트리구조에 필요한 상수.
   4방향 중 하나는 미로로 들어오는 방향이므로 이를 부모 노드로 보고,
   최대 나머지 3방향으로 나갈 수 있으므로 이를 자식 노드로 본다. */
#define MAX_CHILD   3

typedef struct  _node {
    struct  _node *p_parent;

    int     maze_x, maze_y;
    MAZE_STATUS ms[MAZE_DIR_MAX];

    struct  _node *p_child[MAX_CHILD];
} node, *p_node;


#define INIT_NODE(node) \
    do {\
        (node)->p_parent = NULL;\
        (node)->maze_x = (node)->maze_y = 0;\
        (node)->ms[MAZE_DIR_UP] = (node)->ms[MAZE_DIR_DOWN] = (node)->ms[MAZE_DIR_LEFT] = (node)->ms[MAZE_DIR_RIGHT] = MS_UNDEF;\
        (node)->p_child[0] = (node)->p_child[1] = (node)->p_child[2] = NULL;\
    } while(0);\



int init_maze();
int generate_maze();
int gen_maze( node *mz );
node  *make_node();
int   delete_node( node *mz );


#endif  // MAZE_GEN_TREE_H





#include
#include
#include "maze_gen_tree.h"

char    isAlloc[MAZE_WIDTH][MAZE_HEIGHT];
node    mz_start;



int init_maze()
{
    int     x, y;

    INIT_NODE( &mz_start );

    for( x = 0 ; x < MAZE_WIDTH ; x++ )
        for( y = 0 ; y < MAZE_HEIGHT ; y++ )
            isAlloc[x][y] = FALSE;

    isAlloc[0][0] = TRUE;
    mz_start.maze_x = mz_start.maze_y = 0;
    mz_start.ms[MAZE_DIR_UP] = mz_start.ms[MAZE_DIR_LEFT] = MS_WALL;

    return  TRUE;
}



int generate_maze()
{
    node    *mz = &mz_start;

    init_maze();

    gen_maze( mz );

    return TRUE;
}



int gen_maze( node *mz )
{
    int         i, j, rnd, num_child = 0;
    node        *n;
    MAZE_DIR    dir[MAZE_DIR_MAX] = { MAZE_DIR_MAX, MAZE_DIR_MAX, MAZE_DIR_MAX, MAZE_DIR_MAX };

    /* 현재 노드 '*mz'의 주변 상태를 설정한다. 위,아래,좌,우가 벽인지 아닌지, 출발점인지 도착점인지 */
    if( mz->maze_x == 0 )
    {
        if( mz->maze_y == 0 )   // Special Case : Maze Start
            mz->ms[MAZE_DIR_UP] = mz->ms[MAZE_DIR_LEFT] = MS_WALL;
        else if( mz->maze_y == MAZE_HEIGHT-1 )
            mz->ms[MAZE_DIR_DOWN] = mz->ms[MAZE_DIR_LEFT] = MS_WALL;
        else
            mz->ms[MAZE_DIR_LEFT] = MS_WALL;
    }
    else if( mz->maze_x == MAZE_WIDTH-1 )
    {
        if( mz->maze_y == 0 )
            mz->ms[MAZE_DIR_UP] = mz->ms[MAZE_DIR_RIGHT] = MS_WALL;
        else if( mz->maze_y == MAZE_HEIGHT-1 )  // Special Case : Maze Exit
        {
            mz->ms[MAZE_DIR_DOWN] = mz->ms[MAZE_DIR_RIGHT] = MS_WALL;

            return num_child;
        }
        else
            mz->ms[MAZE_DIR_RIGHT] = MS_WALL;
    }
    else
    {
        if( mz->maze_y == 0 )
            mz->ms[MAZE_DIR_UP] = MS_WALL;
        else if( mz->maze_y == MAZE_HEIGHT-1 )
            mz->ms[MAZE_DIR_DOWN] = MS_WALL;
    }

    /*
        재귀함수이기에 depth-first의 방식으로 미로를 만들어 나가게 된다.
        이 경우에 첫번째에 선택되는 방향이 출구로 향하는 경로가 되므로, 선택하는 방향의 순서를 난수화 해야 한다.
        따라서 dir[]에 4가지 방향을 위한 배열을 만들고 각 방향의 순서를 난수로 만들되, 중복되지 않도록 하는 것이다.
    */
    for( i = MAZE_DIR_UP ; i < MAZE_DIR_MAX ; i++ )
    {
        while( 1 )
        {
            rnd = rand()%MAZE_DIR_MAX;
            if( dir[rnd] == MAZE_DIR_MAX )
            {
                dir[rnd] = i;
                break;
            }
        }
    }

    for( i = 0 ; i < MAZE_DIR_MAX ; i++ )
    {
        if( mz->ms[dir[i]] != MS_UNDEF )
            continue;

        switch( dir[i] )
        {
            case MAZE_DIR_UP :
                if( isAlloc[mz->maze_x][mz->maze_y-1] == FALSE )
                {
                    n = make_node();
                    if( n == NULL )
                    {
                        fprintf( stderr, "make_node() failed : %s(%d)\n", __FILE__, __LINE__ );
                        return num_child;
                    }

                    n->maze_x = mz->maze_x;
                    n->maze_y = mz->maze_y-1;
                    n->p_parent = mz;
                    n->ms[MAZE_DIR_DOWN] = MS_PARENT;

                    j = 0;
                    while( mz->p_child[j] ) j++;
                    mz->p_child[j] = n;
                    mz->ms[MAZE_DIR_UP] = MS_CHILD;

                    isAlloc[mz->maze_x][mz->maze_y-1] = TRUE;
                    num_child++;

                    gen_maze( n );
                }
                break;

            case MAZE_DIR_DOWN :
                if( isAlloc[mz->maze_x][mz->maze_y+1] == FALSE )
                {
                    n = make_node();
                    if( n == NULL )
                    {
                        fprintf( stderr, "make_node() failed : %s(%d)\n", __FILE__, __LINE__ );
                        return num_child;
                    }

                    n->maze_x = mz->maze_x;
                    n->maze_y = mz->maze_y+1;
                    n->p_parent = mz;
                    n->ms[MAZE_DIR_UP] = MS_PARENT;

                    j = 0;
                    while( mz->p_child[j] ) j++;
                    mz->p_child[j] = n;
                    mz->ms[MAZE_DIR_DOWN] = MS_CHILD;

                    isAlloc[mz->maze_x][mz->maze_y+1] = TRUE;
                    num_child++;

                    gen_maze( n );
                }
                break;

            case MAZE_DIR_LEFT :
                if( isAlloc[mz->maze_x-1][mz->maze_y] == FALSE )
                {
                    n = make_node();
                    if( n == NULL )
                    {
                        fprintf( stderr, "make_node() failed : %s(%d)\n", __FILE__, __LINE__ );
                        return num_child;
                    }

                    n->maze_x = mz->maze_x-1;
                    n->maze_y = mz->maze_y;
                    n->p_parent = mz;
                    n->ms[MAZE_DIR_RIGHT] = MS_PARENT;

                    j = 0;
                    while( mz->p_child[j] ) j++;
                    mz->p_child[j] = n;
                    mz->ms[MAZE_DIR_LEFT] = MS_CHILD;

                    isAlloc[mz->maze_x-1][mz->maze_y] = TRUE;
                    num_child++;

                    gen_maze( n );
                }
                break;

            case MAZE_DIR_RIGHT :
                if( isAlloc[mz->maze_x+1][mz->maze_y] == FALSE )
                {
                    n = make_node();
                    if( n == NULL )
                    {
                        fprintf( stderr, "make_node() failed : %s(%d)\n", __FILE__, __LINE__ );
                        return num_child;
                    }

                    n->maze_x = mz->maze_x+1;
                    n->maze_y = mz->maze_y;
                    n->p_parent = mz;
                    n->ms[MAZE_DIR_LEFT] = MS_PARENT;

                    j = 0;
                    while( mz->p_child[j] ) j++;
                    mz->p_child[j] = n;
                    mz->ms[MAZE_DIR_LEFT] = MS_CHILD;

                    isAlloc[mz->maze_x+1][mz->maze_y] = TRUE;
                    num_child++;

                    gen_maze( n );
                }
                break;

            default :
                fprintf( stderr, "gen_maze() : dir[] has invalid value : %s(%d)\n", __FILE__, __LINE__ );
        }  // switch(dir[i])
    } // for(i)

    return num_child;
}



node  *make_node()
{
    node  *mz;

    mz = (p_node)malloc(sizeof(node));
    if(!mz)
        return NULL;

    INIT_NODE(mz);

    return mz;
}



int   delete_node( node *mz )
{
    free(mz);

    return TRUE;
}