[C] Echo server/client

대학교 이후로 거의 접해지 못했던 C 를 다시 시작하고 있습니다.

다시 시작하는 이유는 회사에서 C 개발자가 필요하기 때문입니다. 처음이라 진입이 쉽지는 않겠지만 포기하지 않고 계속 하다 보면 언젠가는 되리라 생각합니다.

예제는 http://www.joinc.co.kr/w/Site/win_network_prog/doc/winsock_basic 의 코드를 참조하며 모르는 부분에 주석을 추가하였습니다.

지속적으로 이전에 java로 만들었던 채팅 서버, 메신저 서버 등을 만들어볼 생각입니다.

개발환경

  • IDE: Visual Studio 2012
  • OS: Windows 10

[echo_server.c]

/*
File: echo_server.c
Desc: 에코 서버.
        서버는 클라이언트의 접속 요청을 수락한 후 송신된 메시지를 출력 및 동일 메시지 송신 후 클라이언트의 연결을 종료하는 프로그램
Date: 2016. 04. 27
Author: coozplz@gmail.com
*/
#include <winsock.h>
#include <stdio.h>

// Winsock 라이브러리 로딩을 위한 코드
#pragma comment(lib, "Ws2_32.lib");


#define MAX_PACKET_LEN 512
#define PORT 5552
#define BACK_LOG_SIZE 5

int main()
{
    WSADATA wsaData;
    int status;
    int socketLength;
    int readSize, writeSize;
    SOCKET endPointSocket, clientSocket;

    struct sockaddr_in SocketInfo, ClientSocketInfo;

    char buffer[MAX_PACKET_LEN];

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        printf("WSAStartup error \n");
        return 0;
    }

    endPointSocket = socket(AF_INET, SOCK_STREAM, 0);

    if (endPointSocket == INVALID_SOCKET) {
        printf("endPointSocket is invalid \n");
        return 0;
    }

    printf("Server is running on port %d\n", PORT);

    // 구조체를 초기화한다.
    ZeroMemory(&SocketInfo, sizeof(struct sockaddr_in));
    SocketInfo.sin_family = AF_INET;
    // htons(): Host 시스템에서 Network 로 short 형 데이터를 보낼 때 바이트 오더를 바꿔주는 함수
    SocketInfo.sin_port = htons(PORT); 

    // htonl(): long형 데이터의 바이트 오더를 바꿔주는 함수.
    // INADDR_ANY 는 OS에 설정되어 있는 아이피 어느것이나 바인딩 한다는 의미
    SocketInfo.sin_addr.S_un.S_addr = htonl(INADDR_ANY); 

    status = bind(endPointSocket, (struct sockaddr*)&SocketInfo, sizeof(struct sockaddr_in));

    if (status == SOCKET_ERROR) {
        printf("failed to bind socket \n");
        return 0;
    }

    //
    // backlog 인자는 아직 미결인 연결들에 대한 큐의 늘어날 수 있는 최대 길이를 정의한다.
    // 큐에 도착한 연결 요청들이 꽉 찬다면 클라이언트는 ECONNREFUSED를 가리키는 에러를 받거나 만일 하위 프로토콜이 재전송을 지원한다면, 요청은 재시도가 성공되도록 하기 위해 무시된다.
    //
    status = listen(endPointSocket, BACK_LOG_SIZE);

    if (status == SOCKET_ERROR) {
        printf("failed to listen socket \n");
        return 0;
    }

    while (TRUE) {
        // 클라이언트 구조체 초기화
        ZeroMemory(&ClientSocketInfo, sizeof(struct sockaddr_in));
        socketLength = sizeof(struct sockaddr_in);

        clientSocket = accept(endPointSocket, (struct sockaddr *) &ClientSocketInfo, &socketLength);
        if (clientSocket == INVALID_SOCKET) {
            printf("client socket is invalid\n");
            closesocket(clientSocket);
            continue;
        }

        // inet_ntoa() : 네트워크 바이트 순서의 32비트 값을 Dotted-Decimal notation 의 주소값으로 변환
        // 예 0x6601a8c0: 102.1.168.192
        printf("[Accepted] %s:%d\n", inet_ntoa(ClientSocketInfo.sin_addr), (int)ntohs(ClientSocketInfo.sin_port));
        ZeroMemory(&buffer, sizeof(buffer));
        readSize = recv(clientSocket, (void *)buffer, MAX_PACKET_LEN, 0);

        if (readSize > 0) {
            printf("Message from client > %s\n", buffer);
            writeSize = send(clientSocket, buffer, readSize, 0);
        }
        else {
            printf("Error on reading\n");
        }
        closesocket(clientSocket);
        printf("[Closed] %s:%d\n", inet_ntoa(ClientSocketInfo.sin_addr), (int)ntohs(ClientSocketInfo.sin_port));
    }
    closesocket(endPointSocket);
    WSACleanup();
    return 0;
}

[echo_client.c]

/*
File: echo_client.c
Desc: 에코 클라이언트.
      서버에 접속 요청을 한 후 사용자가 입력한 메시지를 서버에 송신 후 응답 메시지를 출력한 후 소켓 연결이 종료되었는지 판단하고 소켓 연결을 종료한다.
Date: 2016. 04. 27
Author: coozplz@gmail.com
*/

#include <stdio.h>
#include <WinSock2.h>

// Winsock 라이브러리 로딩을 위한 코드
#pragma comment(lib, "Ws2_32.lib");


#define PORT_NUM 5552
#define MAX_LEN 1024

int main(int argc, char **argv)
{
    SOCKET socketFd;
    WSADATA wsaData;
    struct sockaddr_in addr;

    char buffer[MAX_LEN];
    char receiveBuffer[MAX_LEN];

    int status = 0;
    int readSize = 0;
    status = WSAStartup(MAKEWORD(2, 2), &wsaData);

    if (status != NO_ERROR) {
        printf("WSAStart has error\n");
        return 1;
    }

    socketFd = socket(AF_INET, SOCK_STREAM, 0);

    if (socketFd == INVALID_SOCKET) {
        printf("socketFd is invalid\n");
        return 1;
    }
    ZeroMemory(&addr, sizeof(struct sockaddr_in));

    addr.sin_family = AF_INET;
    //
    // inet_addr() : Dotted-decimal 타입을 네트워크 32바이트 순서로 변경
    //
    addr.sin_addr.S_un.S_addr = inet_addr("192.168.10.52");
    addr.sin_port = htons(PORT_NUM);

    status = connect(socketFd, (struct sockaddr_in *)&addr, sizeof(addr));

    if (status == SOCKET_ERROR) {
        printf("connection failed...\n");
        return 1;
    }

    while (TRUE) {
        printf("Connection is established...\n");
        printf("> ");
        fgets(buffer, MAX_LEN - 1, stdin);
        if (strncmp(buffer, "quit\n", 5) == 0) {
            break;
        }

        printf("SEND: %s", buffer);
        send(socketFd, (void *)buffer, strlen(buffer), 0);
        ZeroMemory(&receiveBuffer, sizeof(receiveBuffer));
        readSize = recv(socketFd, (void *)receiveBuffer, MAX_LEN, 0);
        printf("READ = %s\n", receiveBuffer);
        //
        // 소켓연결이 종료되었는지 판단하기 위해 다시 recv() 함수를 호출한다.
        //
        ZeroMemory(&receiveBuffer, sizeof(receiveBuffer));
        readSize = recv(socketFd, (void *)receiveBuffer, MAX_LEN, 0);
        if (readSize <= 0) {
            printf("socket is disconnected by server!!\n");
            break;
        }
    }
    closesocket(socketFd);
    WSACleanup();

    printf("Press any key to exit program.");
    getchar();
    return 0;
}

야구 게임

[게임 방법]
컴퓨터가 1-9까지 서로 다른 숫자 3개를 저장한다.
사용자가 1-9까지의 숫자를 3개 입력 한다.
컴퓨터가 가지고 있는 자리와 숫자가 일치 하면 STRIKE, 숫자만 일치 하면 BALL 이고
그렇지 않은 경우 아무것도 아니다.

[소스 코드]

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>



void main(void)
{
    int computer[3] = { 0, };
    volatile int gamer[3] = { 0, };
    int guess[9] = { 0, };
    int count = 0, i = 0;
    int strike, ball;
    int inputCount = 0;
    char yesno;

    srand(time(NULL));
    puts("야구 게임을 시작합니다.");

    while (1)
    {
        computer[0] = (rand() % 9) + 1;
        computer[1] = (rand() % 9) + 1;
        computer[2] = (rand() % 9) + 1;
        count = 1;
        inputCount = 0;

        if (computer[0] == computer[1]
            || computer[0] == computer[2]
            || computer[1] == computer[2])
            continue;

        // puts("숫자 1-9를 공백으로 분리하여 3개를 입력  ");

        memset(guess, 0, sizeof(guess));

        // printf("%d %d %d \n", computer[0], computer[1], computer[2]);


        while (1)
        {
            strike = ball = 0;

            printf("\n 3개의 숫자[0-9]를 입력하세요: ");
            scanf("%d %d %d", &gamer[0], &gamer[1], &gamer[2]);



            if (computer[0] == gamer[0]) {
                strike++;
            }
            else if (computer[0] == gamer[1] || computer[0] == gamer[2]) {
                ball++;
            }

            if (computer[1] == gamer[1]) {
                strike++;
            }
            else if (computer[1] == gamer[0] || computer[1] == gamer[2]) {
                ball++;
            }

            if (computer[2] == gamer[2])  {
                strike++;
            }
            else if (computer[2] == gamer[1] || computer[2] == gamer[0]) {
                ball++;
            }

            guess[gamer[0] - 1] = 1;
            guess[gamer[1] - 1] = 1;
            guess[gamer[2] - 1] = 1;


            printf("\n[%2d회]  %d %d %d  S: %d\tB: %d \n\n", count, gamer[0], gamer[1], gamer[2], strike, ball);
            if (strike == 3)  {
                printf("삼진 아웃 입니다.\n\n");
                break;
            }

        }
        printf("게임을 계속하겠습니까(y/n)? ");
        scanf("%c", &yesno);
        if (yesno == 'N' || yesno == 'n')
            break;

    }

}

atoi 구현

[출처] http://bluemir7.tistory.com/91

C 라이브러리에 보면
문자열을 숫자로 변환해주는 atoi 이라는 함수가 있다.
하지만 특정 시스템에 따라서는 해당 함수를 지원하지 않는 경우가 있는데
이때, atoi 함수를 구현하여 사용하여야 한다.
구현 방법 또한 단순하다. 단 지금 정리하는 구현 방법이 최적의 방법이라고 볼 수는
없을 것이다. 또 다른 방법이 존재할 지도…;;
구현 내용은 다음과 같다.

[알고리즘]
1. 변환하고자 하는 문자열을 가져온다.
2. 문자열을 첫 지점부터 ”를 만날때 까지 각 자리 문자를 체크하며 이때 해당 문자는 ‘0’ 보다 크고 ‘9’ 작아야 한다. [조건] 문자 != ” && (문자 >= ‘0’ && 문자 <= '9') 조건을 만족할 경우 '3' 으로 만족하지 않을 경우 '4'
3. 한자리 문자를 가져오며 다음 식에 의해 숫자 값으로 변환한다.
[식] y = ((int)x – (int)'0') + (10 * y)
x : 대상 문자
y : 변환된 숫자
다음 문자 처리를 위해 '2'으로..
4. 최종 변환됫 값을 반환한다.

[소스 코드]

int atoi(char * cTarget)
{

    int iChangeValue = 0;
    while (*cTarget != '' && (*cTarget >= '0' && *cTarget <= '9'))
    {
        // 현재 문자 변환시켜 기존에 있는 숫자에 더해준다.
        iChangeValue = (iChangeValue * 10) + ((int)*cTarget - (int)'0');
        cTarget++;   // 다음 문자로 이동
    }
    return iChangeValue;
}

위에서 ((int)*cTarget – (int)'0') 는 문자의 아스키 값을 숫자로 변환 했을때의 값을 계산하여
숫자로 치환 한것이다. 즉, *cTarget 값이 '1'이라면 '1'은 아스키 값이 49 이고 '0'은 아스키값이 48 이므로
49 -48을 하게되면 1이 반환된다.
(10 * iChangeValue) 이 부분은 기존 값을 한자리씩 앞으로 옮기는 역활을 담당한다.
즉, 현재 값이 2 이고 기존에 1이 있다면 (1 * 10) + 2 가 되므로 12를 반환하게된다

Eclipse Scanf

[출처] http://blog.naver.com/PostView.nhn?blogId=bboy6604&logNo=20105208581

이클립스를 사용하여 C언어 공부를 하려고 하는데 초급 예제다 보니 Scanf 명령을 사용하여 데이터 입력값을 처리하는데 이상하게 VisualStudio에서는 잘되던데 Eclipse에서만 하면 입력전에 어떤 데이터를 입력하라고 하는 부분이 출력이 안되어 검색하니 printf 이후 버퍼를 비워야 한다고 합니다.

fflush(stdout);

int main()
{
    int kor[2], eng[2];
    int total[2];

    double avg[2];
    int i;

    for(i=0; i<2; i++)
    {
        printf(" 사용자 국어 영어 :\n");
//        fflush(stdout);
        scanf("%d %d", kor + i, eng + i);
    }

    for(i=0; i<2; i++)
    {
        total[i] = kor[i] + eng[i];
        avg[i] = total[i] / 2.;
    }
    for(i=0; i<2; i++)
        printf("%d %d %d %5.2lf\n", kor[i], eng[i], total[i], avg[i]);
    return 0;
}

12번 라인 주석 후 출력 결과
빨간 글씨는 사용자 입력 데이터입니다.

50 50
50 50

사용자 국어 영어 :
사용자 국어 영어 :
50 50 100 50.00
50 50 100 50.00

 

12번 라인 주석 해제 후 출력 결과

 

사용자 국어 영어 :
50 50
사용자 국어 영어 :
50 50
50 50 100 50.00
50 50 100 50.00

포인터 정리 1일차

// 2월 4일 수업 내용 정리
1. 포인터의 등장 배경

/*
    포인터를 사용하지 않은 경우     
    i의 값을 20으로 변경하고 단순히 출력하는 예제
*/
void main() 
{
    int i =10;
    i = 20; // 
    printf("%d\n", i);    
}

위의 코드를 포인터를 쓰도록 변경한다.

/*
    포인터를 사용하는 경우
    문제점
    1. 메모리를 낭비한다.
        - 이전 소스인 int만 사용하는 경우에는 4Byte -> 포인터 8Byte
    2. 속도가 느리다.
        - Assembly 코드에서 연산이 늘어나기 때문에 느리다.
*/


void main()
{
    int i=10;
    int *p = &i; // i의 주소값을 넘긴다.
    *p = 20;
    printf("%d\n", i);
}

그렇다면 메모리 사용량도 많고 속도도 느린 포인터를 왜 사용해야 하는가?

/*


    문제가 많은 포인터를 사용해야 하는 이유
    모든 소스를 메인 함수에만 작성하는 것은 아니다.
    메인 함수에는 자료구조를 그리고 알고리즘 부분은 함수로 작성한다.
    1번 예제에서 메인 함수에 선언된 i의 값을 다른 함수에서 
    변경할 수 있는가?
    기본적으로는 불가능 하다. 지역변수는 사용할 수 있는 범위가
    지정되어 있기 때문이다.
    만약

    foo() 
    {
        int i;
        i = 20; 
    }


    위와 같이 변경 하게 되면 다른 지역 변수를 변경하게 된다.(main변경X)
    int i;
    void foo()
    {
        i = 20; 
    }
    main()
    {
        int i = 10;
        foo();
        printf("%d\n", i); 
    }
    위의 코드를 실행하게 되면 10이 출력된다.
    {} 안에서 컴파일러는 i에 해당하는 지역변수를 먼저 찾고
    지역변수가 없다면 전역 변수를 찾게 된다.
    그러므로 main변경 X     
    그렇기 때문에 메모리를 더 사용하고 속도도 느린 포인터를
    사용하게 되는 것이다.(모듈화 때문)
*/

#if 1
void foo(int *p) 
{
    *p = 20;
}

void main()
{
    int i=10;    
    foo(&i);
    printf("%d\n", i);
}

#endif

포인터를 사용함에 있어 중요하게 생각해야 하는 부분은 대상체의 크기 이다.

/*
    포인터를 사용하는데 있어 반드시 생각해야 할 것
    포인터는 주소값을 저장하는 것이다.
    포인터 = 4Byte(주소값)    
    포인터 + 1 = 포인터 + (대상체의 사이즈 * n)
    대상체의 크기를 int 는 4Byte , Char 는 1byte 로 생성된다.
*/


#if 1
// 포인터 대상체 사이즈 확인
void main()
{
    double i=10;    
    double *p = &i;
    printf("double size = %d\n",sizeof(double));    
    printf("p size = %d\n", sizeof(p));                // 포인터의 크기
    printf("p+1 size = %d\n", sizeof(p+1));            // 포인터의 크기
    printf("*(p+1) size = %d\n", sizeof(*(p+1)));    // 대상체의 크기
}
#endif






```c
/*
    포인터를 이용하여 Little Endian 또는 Big Endian 을 찾는 방법
    int에 대한 타입을 char * 타입으로 강제 치환 하게 되면 3Byte는
    잘리고 남은 1Byte만 출력되게 된다. little endian 인 경우 
    뒤에 1Byte가 출력되고 Big Endian 은 앞에 1Byte가 출력된다.
*/


main() 
{
    int i = 0x12345678;
    char *p = (char*)&i; // 강제로 치환하면서 3Byte를 버린다.
    printf("%x\n", *p); // OUput => 78
}

포인터 또는 배열을 영어로 말하면?

/*
    int a[5] => array of 5 int
    int *p    => pointer to int
    int * foo(void) => function returnning pointer to int

    2차원 배열
    2차원 배열을 논리적으로는 2차원 이지만 물리적으로는 1차원이다.
    int a[2][2] =>  int 4 Byte * 4 => 16Byte
    int a[0] => int 4 Byte * 2 = > 8Byte
*/


main() 
{
    int a[2][3] = {{1,2,3}, {3,4,5}};
    printf("%d\n", sizeof(a));
    printf("%d\n", sizeof(int[2][3]));
    printf("%d\n", sizeof(a) / sizeof(a[0])); // 행의 갯수    
}

C 테트리스

winapi.co.kr 에 있는 테트리스 강좌를 실행 했습니다.
실행하는데 필요한 함수 및 라이브러리를 추가했습니다.

[소스코드]

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <conio.h>
#include <Windows.h>


typedef enum { false, true } BOOL;
#define TOTAL_BLOCK  7  // 전체블럭수
#define BW 10   // 테트리스판의 가로
#define BH 20    // 테트리스판의 높이
#define BX 5    //
#define BY 1
#define LEFT 75
#define RIGHT 77
#define UP 72
#define DOWN 80
#define ESC 27
#define clscr() system("cls")

typedef enum {
    NOCURSOR
    , SOLIDCURSOR
    , NORMALCURSOR
} CURSOR_TYPE;

enum {
    EMPTY
    , BRICK
    , WALL
};


void DrawScreen();
void DrawBoard();
void setCursorType(CURSOR_TYPE c);
void TestFull();
void gotoxy(int x, int y);
void PrintBrick(BOOL Show);
int GetAround(int x, int y, int b, int r);
BOOL MoveDown();
BOOL ProcessKey();

struct Point
{
    int x, y;
};

char *arTile[] = { ".", "■", "□" };

// 테트리스 판의 전체크기
int board[BW + 2][BH + 2];

// 현재 진행중인 블럭
int brick;

// 회전수
int rot;
int nx, ny;
struct Point Shape[][4][4] = {
    // 
    // 0.■■■■
    { { 0, 0, 1, 0, 2, 0, 0, 1 },
    { 0, 0, 0, 1, 0, 2, -1, 0 },
    { 0, 0, 0, -1, -1, 0, -2, 0 },
    { 0, 0, 1, 0, 0, -1, 0, -2 }
    },
        // 1. ■■
        //    ■■
    { { 0, 0, 1, 0, 0, 1, 1, 1 },
    { 0, 0, 1, 0, 0, 1, 1, 1 },
    { 0, 0, 1, 0, 0, 1, 1, 1 },
    { 0, 0, 1, 0, 0, 1, 1, 1 } },
    //            
    //  2.■■        
    //      ■■   
    {
        { 0, 0, -1, 0, 0, -1, 1, -1 },
        { 0, 0, 0, 1, -1, 0, -1, -1 },
        { 0, 0, -1, 0, 0, -1, 1, -1 },
        { 0, 0, 0, 1, -1, 0, -1, -1 }
    },
        //
        //    3. ■■
        //     ■■
    { { 0, 0, 1, 0, 0, -1, -1, -1 },
    { 0, 0, 0, 1, 1, 0, 1, -1 },
    { 0, 0, 1, 0, 0, -1, -1, -1 },
    { 0, 0, 0, 1, 1, 0, 1, -1 } },
    //
    //  4.■■■
    //    ■
    { { 0, 0, -1, 0, 1, 0, -1, -1 },
    { 0, 0, 0, -1, 0, 1, -1, 1 },
    { 0, 0, -1, 0, 1, 0, 1, 1 },
    { 0, 0, 0, -1, 0, 1, 1, -1 } },

    //
    //   5.■■■   
    //       ■     

    { { 0, 0, 0, 1, -1, 1, -2, 1 },
    { 0, 0, 1, 0, 1, 1, 1, 2 },
    { 0, 0, 0, -1, 1, -1, 2, -1 },
    { 0, 0, -1, 0, -1, -1, -1, -2 } },
    //
    //   6. ■■■
    //        ■
    { { 0, 0, -1, 0, 1, 0, 0, 1 },
    { 0, 0, 0, -1, 0, 1, 1, 0 },
    { 0, 0, -1, 0, 1, 0, 0, -1 },
    { 0, 0, -1, 0, 0, -1, 0, 1 } }
};

int main() {
    int nFrame, nStay;
    int x, y;
    setCursorType(NOCURSOR);
    // 
    // 초기 화면 배열을 생성한다.
    //
    for (x = 0; x<BW + 2; x++) {
        for (y = 0; y<BH + 2; y++) {
            board[x][y] = EMPTY;
            //블럭의 테두리를 그리게 채운다.
            if (y == 0
                || y == BH + 1
                || x == 0
                || x == BW + 1) {
                board[x][y] = WALL;
            }

            //board[x][y] = (y==0||y==BH+1|| x==0 || x ==BW+1) ? WALL: EMPTY;
        }
    }
    // 초기 화면을 그린다.
    DrawScreen();
    nFrame = 20;

    //
    // 게임을 진행한다. 
    //
    //        
    while (true) {
        //
        // 무한 루프를 반복하며 게임을 진행한다.
        // 무한 루프에서 빠져 나오면 게임을 종료한다. 
        //
        srand(time(NULL));

        // 랜덤하게 벽돌을 생성한다.
        brick = rand() % ((sizeof(Shape) / sizeof(Shape[0])) + 1);

        // 현재 가로 위치        
        nx = BW / 2;

        // 생성된 세로 위치
        ny = 2;

        // 회전수
        rot = 0;

        PrintBrick(true);

        // 만약 벽돌이 화면에 가득차면 게임을 종료한다.
        if (GetAround(nx, ny, brick, rot) != EMPTY) break;

        nStay = nFrame;

        // 키 입력을 받아 이벤트를 처리한다.
        while (true) {
            if (--nStay == 0) {
                nStay = nFrame;
                if (MoveDown()) break;
            }

            if (ProcessKey()) break;

            Sleep(1000 / 20);
        }
    }

    clscr();
    gotoxy(30, 12); puts("G A M E O V E R");
    setCursorType(NORMALCURSOR);
}




BOOL ProcessKey() {
    // 전체 회전수
    int ch, trot;
    if (kbhit()) {
        ch = getch();
        if (ch == 0xE0 || ch == 0) {
            ch = getch();
            switch (ch) {
            case LEFT:
                if (GetAround(nx - 1, ny, brick, rot) == EMPTY) {
                    PrintBrick(false);
                    nx--;
                    PrintBrick(true);
                }
                break;

            case RIGHT:
                if (GetAround(nx + 1, ny, brick, rot) == EMPTY) {
                    PrintBrick(false);
                    nx++;
                    PrintBrick(true);
                }
                break;

            case UP:
                trot = (rot == 3 ? 0 : rot + 1);
                if (GetAround(nx, ny, brick, trot) == EMPTY) {
                    PrintBrick(false);
                    rot = trot;
                    PrintBrick(true);
                }
                break;

            case DOWN:
                if (MoveDown()) {
                    return true;
                }
                break;
            }
        }
        else {
            switch (ch) {
            case ' ':
                while (MoveDown() == false) {

                }
                return true;
            }
        }
    }
    return false;
}


//
// 블럭을 그리고 그려진 블럭을 지운다.
//
void PrintBrick(BOOL Show) {
    int i;
    for (i = 0; i<4; i++) {
        gotoxy(BX + (Shape[brick][rot][i].x + nx) * 2, BY + Shape[brick][rot][i].y + ny);
        puts(arTile[Show ? BRICK : EMPTY]);
    }
}

//
// 테트리스 화면을 그린다.
//


void DrawScreen() {
    //int x, y;
    for (int x = 0; x<BW + 2; x++) {
        for (int y = 0; y<BH + 2; y++) {
            gotoxy(BX + x * 2, y);
            puts(arTile[board[x][y]]);
        }
    }

    // COORD coord; coord.X = 50; coord.Y=3;
    // SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
    gotoxy(50, 3);
    puts("Tetris Ver 1.0");

    gotoxy(50, 5);
    puts("좌우:이동, 위:회전, 아래:내림");

    gotoxy(50, 6);
    puts("공백: 전부 내림");

    gotoxy(1, 25);
}

//
// 배경을 뺀 나머지 부분을 그린다.
//
void DrawBoard() {
    int x, y;

    // 테두리를 제외한 부분을 그린다.
    for (x = 1; x < BW + 1; x++) {
        for (y = 1; y < BH + 1; y++) {
            gotoxy(BX + x * 2, BY + y);
            puts(arTile[board[x][y]]);
        }
    }
}

//
// 주변에 벽돌이 있는지 확인한다.
//
int GetAround(int x, int y, int brick, int rotation) {
    int k = EMPTY;
    for (int i = 0; i < 4; i++) {
        int t = board[x + Shape[brick][rotation][i].x][y + Shape[brick][rotation][i].y];
        if (t > k) {
            k = t;
        }
    }
    return k;
}

//
// 커서의 종류를 변경한다.
//
void setCursorType(CURSOR_TYPE c) {
    CONSOLE_CURSOR_INFO curInfo;
    switch (c) {
    case NOCURSOR:
        curInfo.dwSize = 1;
        curInfo.bVisible = false;
        break;

    case SOLIDCURSOR:
        curInfo.dwSize = 100;
        curInfo.bVisible = true;
        break;

    case NORMALCURSOR:
        curInfo.dwSize = 20;
        curInfo.bVisible = true;
        break;
    }
}

//
// 콘솔창의 좌표를 파라미터 값으로 받은 x, y로 이동한다.
//
void gotoxy(int x, int y) {
    COORD coord;
    coord.X = x;
    coord.Y = y;
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}

//
// 블럭을 아래로 내린다.
//
BOOL MoveDown() {
    if (GetAround(nx, ny + 1, brick, rot) != EMPTY) {
        TestFull();
        return true;
    }

    PrintBrick(false);
    ny++;

    PrintBrick(true);
    return false;
}

void TestFull() {
    int i, x, y, ty;
    for (i = 0; i < 4; i++) {
        board[nx + Shape[brick][rot][i].x][ny + Shape[brick][rot][i].y] = BRICK;
    }

    for (y = 1; y < BH + 1; y++) {
        for (x = 1; x < BW + 1; x++) {
            if (board[x][y] != BRICK) break;
        }


        if (x == BW + 1) {
            for (ty = y; ty > 1; ty--) {
                for (x = 1; x < BW + 1; x++) {
                    board[x][ty] = board[x][ty - 1];
                }
            }
            DrawBoard();
            Sleep(200);
        }
    }
}

winapi.co.kr API강좌 따라하기–#1

처음에 Visual Studio 6.0 을 설치 해서 따라하다가 이상하게 프로젝트 실행은 되는데 저장된 프로젝트를 다시 실행하니 종료하라는 메시지가 출력 되서 포기…

두번째 이클립스에서 CPP 컴파일러가 있는 것을 확인하고 설치 해서 설정하고 했지만 뭔가 이상함… 포기

세번째 Visual Studio 2010 에서 시도. API 강좌를 그대로 복사해서 붙여 넣기를 했지만 실패.

뭔가 이상하다. 열심히 구글링을 했다. 그랬더니 프로젝트 구분을 잘못 만들었다는 문제에 직면 Win32 ConsoleApplication 이 아니라 Win32 Application 으로 만들어야 한다는걸 파악함.

컴파일 및 빌드는 정상적으로 되었는데 윈도우 창이 출력 안됨. (에이… 망할)

네이버 카페에서 해답을 찾음.

원본

#include 
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage,WPARAM wParam,LPARAM lParam);
LPSTR lpszClass = "First";
HINSTANCE g_hInst;
int APIENTRY WinMain(HINSTANCE hInstance, 
                     HINSTANCE hPrevInstance,
                      LPSTR lpszCmdParam,
                      int nCmdShow)

수정

#ifdef UNICODE
       #undef UNICODE
#endif 
#define _MBCS
#include 
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage,WPARAM wParam,LPARAM lParam);
LPSTR lpszClass = "TextOut";
// 추가 부분
#pragma comment( linker, "/subsystem:windows")
HINSTANCE g_hInst;
int APIENTRY WinMain(HINSTANCE hInstance, 
HINSTANCE hPrevInstance,
LPSTR lpszCmdParam,
int nCmdShow)

이제 정상적으로 된다.