안녕하십니까? 이번 포스팅에서는 지뢰찾기 구현을 위해 제가 만든 함수들을 살펴보겠습니다. 원래는 한꺼번에 설명하려고 했습니다만 워낙 소스코드가 길다보니 따로 빼서 설명하는 것이 좀더 나을 것 같아 먼저 설명드리겠습니다.

지뢰찾기에 쓰인 함수들은 다음과 같습니다.

void Createlandmine(void); - 지뢰배치함수

void Createlandmine2(void);  - 지뢰를 이용하여 기타 타일을 배치하는 함수

void CreateButton(void);  - 버튼을 생성하는 함수

void sp_check(void);  - 주변에 지뢰가 없는 타일(공백타일)을 체크하는 함수

void victoryCheck(void); - 승리조건에 도달했는가를 체크하는 함수

Createlandmine와 Createlandmine2함수는 앞 포스팅에서 설명드렸으니 넘어가겠습니다. 자 이제 차례대로 함수를 살펴 봅시다.

- void CreateButton(void)

함수명 그대로 버튼을 만드는데 쓰이는 함수입니다. 

- int i,j,k=0;

  int m = 0;

  int x=30,y=30;

for문에 쓰일 i,j 변수와 OpenClose_Tile, flagTile에 쓰일 인덱스변수 k를 선언하였습니다. OpenClose_Tile은 눌러진 타일인지, 안눌러진 타일인지의 여부를 저장하는 정수타입 전역 변수 배열 입니다. flagTile은 깃발이 있는지 없는지의 여부를 저장하는 정수타입 전역 변수 배열입니다.

m은 buttonHwnd의 인덱스 변수 겸 메뉴 이름입니다. buttonHwnd은 버튼들의 핸들을 저장시키는 핸들 타입 전역 변수 배열입니다.

x와 y는 버튼을 생성시킬 때의 좌표를 뜻합니다.

- for(i = 0; i <9 ;i++)

for(j = 0; j <9 ;j++)

초급지뢰찾기는 9*9 형태이므로 이 형태로 버튼을 만들어 출력하기 위해 2중 for문을 넣었습니다.

- buttonHwnd[m] = CreateWindow(L"button",NULL,WS_CHILD | WS_VISIBLE | BS_BITMAP,x,y,18,18,Global_hWnd, (HMENU) m,Global_hInstance,NULL);

CreateWindow함수로 버튼을 생성하여 buttonHwnd에 저장시킵니다. 이 때 버튼의 좌표는 아까 선언한 x와 y입니다. 크기는 18*18이이고 이름은 m입니다. 인자들을 보면 Global_hWnd와 Global_hInstance가 들어있는 것을 볼 수 있는데 Global_hWnd는 WndProc함수의 hWnd인자를 사용하기 위해 선언한 전역변수이고 Global_hInstance는 WinMain의 hInstance인자를 사용하기 위한 전역변수입니다.

그다음 눈에 띄는 인자는 바로 BS_BITMAP 입니다. MSDN의 설명을 봅시다.

버튼에 비트맵을 표시한다는 옵션이지요. 버튼에 원하는 색상을 주기 위해 이 옵션을 넣었습니다.

- SendMessage(buttonHwnd[m], BM_SETIMAGE,IMAGE_BITMAP,(LPARAM)LoadBitmap(Global_hInstance, MAKEINTRESOURCE(Tile)));

새로운 함수가 나왔습니다. 이 함수를 살펴봅시다.

말그대로 메세지를 보내는 함수입니다. 인자는 WndProc의 인자와 똑같습니다.

정리하면 이 SendMessage함수는 hWnd값의 윈도우 창에 메세지를 보내는 것입니다. 그러면 받은 hWnd값의 윈도우 창은 WndProc에서 받은 메세지를 처리하게 됩니다.

여기서 hWnd는 각 버튼의 핸들입니다. Msg로는 BM_SETIMAGE가 들어갔는데 이 메세지는 버튼의 이미지를 설정하는 메세지라고 보시면 됩니다. 세번째와 네번째 인자는 메세지의 추가정보 이지요. 먼저 wParam에는 IMAGE_BITMAP이 들어갔습니다. 앞서 메세지에서 BM_SETIMAGE인자를 넣었는데 그냥 이미지하면 커서인지, 아이콘인지, 비트맵인지 구별을 못하므로 비트맵 이미지를 버튼에 설정하기 위해 세번째 인자에 IMAGE_BITMAP가 들어갔습니다. 

마지막 인자에선 LoadBitmap함수가 쓰였습니다. 말 그대로 이함수는 비트맵을 불러오는 함수인데 LoadIcon과 맥락이 비슷하다고 보시면 됩니다.  인자로는 MAKEINTRESOURCE(Tile)이 쓰였습니다. 즉 비트맵리소스를 가져오신다고 보시면 됩니다. 이 리소스는 나중에 지뢰찾기 코드를 설명할 때 자세히 설명드리겠습니다.

마지막으로 LoadBitmap함수는 반환값이 HBITMAP이므로 (LPARAM)으로 강제형변환을 해주었습니다.

정리하면 이 sendMessage함수는 버튼의 BS_BITMAP 옵션을 사용할 때 호출되는 BM_SETIMAGE에 추가정보를 주기 위해 사용되었다고 보시면 됩니다. 이렇게 보낸 BM_SETIMAGE메세지에 의해 버튼은 비트맵 이미지를 받아 출력하게 됩니다.

다음으로 넘어갑시다.

- OpenClose_Tile[k] = 1;

  flagTile[k] = 1;

초기의 OpenClose_Tile, flagTile의 인덱스들의 값을 1로 설정합니다. 즉 OpenClose_Tile의 1은 버튼이 눌려지지 않은 상태임을 나타내고 flagTile의 1은 깃발이 꽃혀있지 않다는 것을 의미합니다.

- k = k+1;

  m += 1;

다음 인덱스를 지정하기 위해 변수들을 1씩 증가시킵니다.

- x += 18;

다음 버튼을 만들기 위해 x의 좌표를 버튼의 크기만큼 증가시킵니다. 이렇게하면 버튼이 오른쪽으로 하나씩 생성됩니다.

- x = 30;

  y += 18;

한줄의 버튼을 다 만들었다면 다음 줄의 버튼을 만들기 위해 x를 처음 버튼위치인 30으로 초기화 시키고, y를 버튼의 크기만큼 증가시킵니다.

최종적으로 이 CreateButton함수가 처리되고 나면 각각의 버튼 이름이 (0~80)인 버튼 81개가 생성됩니다.

다음 함수로 넘어갑시다.

- void sp_check(void)

이 함수는 모든 타일을 검사하여 타일이 9값이고 눌러진 상태일경우, 즉 주변에 지뢰가 없는 타일의 버튼이 벗겨진 경우 그 타일의 주변의 버튼을 없애는 기능을 합니다.

기존 지뢰찾기에서는 누르면 소리가 나면서 버튼이 크게 벗겨지지요? 그것을 구현한 겁니다.

- int i;

81개의 타일을 검사하기 위한 인덱스 변수를 선언합니다.

- for(i=0;i<81;i++)

81개의 타일을 검사하기 위해 선언하였습니다.

- if(buttonValue[i] == 9 && OpenClose_Tile[i] == 0)

buttonValue의 값이 9이고 OpenClose_Tile의 값이 0일 경우 if문을 실행합니다. 여기서 buttonValue는 Createlandmine2함수로 만들어진 arr[9][9]에 들어있는 값을 한 배열에 저장하기 위해 선언한 정수형 전역 변수 배열 입니다.

OpenClose_Tile의 값은 1과 0으로 나뉘어 지는데 1은 버튼이 안눌러진 상태이고 0은 버튼이 눌러진 상태를 의미합니다. 

즉 정리하면 현재 타일이 버튼이 벗겨진 상태의 9값 타일 인 경우를 의미합니다.

이 다음부터는 전 포스팅에서 얘기했던 Createlandmine2의 알고리즘을 이용하여 주변 타일을 삭제하고 OpenClose_Tile의 값을 0으로 지정해주는 코드입니다. 그러므로 일부분만 설명드리겠습니다.

- if(i <= 8)

switch(i)

case 0:

if(DestroyWindow(buttonHwnd[i+1]))

OpenClose_Tile[i+1] = 0;

if(DestroyWindow(buttonHwnd[i+9]))

OpenClose_Tile[i+9] = 0;

if(DestroyWindow(buttonHwnd[i+10]))

OpenClose_Tile[i+10] = 0;

break;

case 8:

.......

break;

default:

break;

i가 8보다 작거나 같을 경우입니다. 아까 CreateButton함수로 인해 버튼들의 핸들은 0~80까지의 인덱스들에 각각 저장되어있습니다. 즉 i가 8보다 작거나 같다는 것은 첫번째 줄을 의미합니다.

switch(i)는 i값에 따라 처리를 다르게 하기 위해 선언하였습니다. 아까의 Createlandmine2알고리즘으로 따지자면 j값을 의미합니다.

이 i값이 0이라면 i=0,j=0을 뜻하고 이 i값이 8이면 i=0,j=8을 뜻하고 그 외의 값(1~7)이라면 i=0, j= (1~7)까지를 나타내지요.

자 이제 처리부분을 한 번 봅시다.

DestroyWindow함수를 사용하여 핸들을 삭제합니다. 이 함수는 말그대로 인자에 해당하는 핸들을 가진 윈도우 창을 부수는 함수입니다. 즉 버튼을 삭제한다는 뜻입니다. 버튼이 삭제된다는 것은 벗겨진다는 것을 의미하지요. 그리고 버튼을 삭제한 이후 해당하는 OpenClose_Tile의 값을 0으로 줍니다. 벗겨졌으니까 OpenClose_Tile의 값을 바꿔주어야 겠지요.

여기서 궁금점이 하나 생길 수 있는데 그것은 바로 if문입니다. 이러한 if문을 준 이유는 버튼을 지우다보면 이미 버튼이 지워진 부분도 다시 버튼을 지우고 OpenClose_Tile의 값을 0으로 주기 때문에 이를 방지하기 위해 if문을 넣었습니다. if문이 실패하면 OpenClose_Tile에 값을 지정하지 않도록 하기 위해서입니다. 사실 if문이 있든 말든 동작에 지장은 없기 때문에 이 부분은 딱히 신경쓰지 않으셔도 됩니다. 편하실 대로 하십시요.

나머지 case와 default는 비슷하기 때문에 생략하였습니다.

- else if(i >= 72)

switch(i)

case 72:

......

break;

case 80:

......

break;

default:

......

break;

i가 72보다 크거나 같을 경우입니다. 즉 마지막 줄을 의미합니다. i값이 바꼈으므로 case의 값도 그에 맞게 바꿔주어야 합니다. 처리코드는 비슷하기에 생략하였습니다.

- else 

switch(i%9)

case 0:

......

break;

case 8:

......

break;

default:

......

break;

그 외의 줄을 처리하는 부분입니다. 즉 9<= i <= 71 인 부분이지요.

여기서 하나 유심히 보셔야 할부분은 switch(i%9)입니다. i값에 9를 나눈 나머지 값을 이용하여 case가 작동합니다. 이렇게 한 이유는 i값의 범위가 크다보니 일일이 case문을 작성해야하는데 너무 비효율적이라 생각하였기 때문입니다. i를 9로 나눈 나머지 값이 0이라면 첫번째 열을 의미하고, 나머지가 8이라면 마지막 열을 의미하고 그 외의 나머지 값은 중간열을 의미하므로 훨씬 간편하게 case문을 작성할 수 있습니다.

이제 마지막 함수를 살펴봅시다.

- void victoryCheck(void)

승리조건에 도달하였는지를 체크하는 함수입니다. 원리는 열려진 타일의 개수를 검사하여 열려진 타일이 10개(지뢰개수)일 경우 승리 메세지박스가 출력됩니다. 

- int i;

  int tilecount = 0;

  검사를 하기 위한 인덱스 변수 i와 타일의 개수를 저장시킬 변수 tilecount를 선언하였습니다.

- for(i=0;i<81;i++)

if(OpenClose_Tile[i] == 1)

tilecount = tilecount+1;

타일을 검사하여 열린 타일의 개수를 체크합니다.

- if(tilecount == 10)

MessageBox(Global_hWnd,L"지뢰를 다찾았습니다.  리!",

L"승리!",MB_ICONQUESTION | MB_OK);

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

if(buttonValue[i] == 0)

DestroyWindow(buttonHwnd[i]);

타일의 개수가 10개일 경우 승리 메세지박스를 띄우고 for문을 사용하여 남아있는 버튼을 전부 삭제합니다.

여기까지가 제가 작성한 함수들입니다.  다음포스팅에서는 리소스를 보여드리도록 하겠습니다.

Posted by englishmath
,

안녕하십니까? 이번 포스팅에서는 지뢰찾기의 알고리즘에 대해 간단히 설명하겠습니다. 먼저 소스코드를 봅시다.

보시다시피 Createlandmine2 함수가 제법 깁니다. 일단 차례대로 설명드리겠습니다.

- #include <stdlib.h>

srand함수를 사용하기 위해 선언하였습니다.

- #include <time.h>

time함수를 사용하기 위해 선언하였습니다.

- void Createlandmine();

지뢰를 랜덤으로 생성하는 함수입니다.

 - void Createlandmine2();

생성된 지뢰를 이용해 주변 타일들의 값을 생성하는 함수입니다.

 - void Print_arr(void);

만든 타일을 보여주는 함수입니다.

- int arr[9][9];

타일 값을 저장하기 위한 2차원 배열을 전역변수로 선언하였습니다.

이제 main함수를 살펴봅시다.

- srand((int)time(NULL));

srand함수가 나왔습니다. MSDN에서 이 함수를 살펴봅시다.

이 함수는 쉽게 얘기하면 난수 생성을 위한 seed값을 설정해주는 함수입니다.

난수란 특별한 규칙이 없는 임의의 수를 뜻하지요. 즉 랜덤한 값입니다. 

그렇다면 시드는 무엇을 의미할까요? 

기본적으로 컴퓨터에서 난수를 생성한다고 하면 컴퓨터는 내부의 난수표를 이용하여 난수를 생성합니다. 그런데 문제는 이 난수표 자체가 고정되어 있음으로 다음에 다시 난수표를 쓰면 항상 같은 순서로 숫자가 나오게 됩니다.

그래서 이를 방지하기 위해 보통 난수표를 여러개 만들어 놓고 매번 다른 난수표를 읽도록 하는데 이 난수표를 선택하는 기준을 시드라고 합니다. 그런데 또 여기서 문제가 발생하는 것이 이 시드값이 똑같으면 컴퓨터는 똑같은 난수표를 읽게 됩니다. 그러므로 우리는 이 시드값을 랜덤으로 주어 매번 다른 난수표를 읽도록 해야 합니다.

이 시드값을 랜덤으로 주는 방법은 여러가지가 있겠지만 대표적으로 시간을 이용하는 방법이 있습니다. 시간을 이용하여 시드값을 주기위해 여기서는 time함수를 사용하였습니다. time함수를 살펴봅시다.

MSDN의 설명에 의하면 이 time함수는 1970년 1월 1일 자정 이후 경과된 시간을 반환하는 함수라고 나와있습니다.  인자로 받는 timer는 반환값을 저장하는 포인터 변수입니다.  우리는 따로 저장할 필요가 없으므로 매개변수에 NULL값을 주었습니다.

이렇게 하면 시간은 고정될 수 없는 값이므로 시드는 항상 랜덤일 것이고, 시드가 항상 랜덤이므로 시드를 이용해 뽑아내는 난수도 랜덤이 될 것입니다. 추가로 time함수가 반환하는 값은 time_t인데 srand에서 받는 seed값은 int이므로 강제형변환을 이용하였습니다. 다음으로 넘어갑시다.

- printf("지뢰 생성, 지뢰 == 0\n\n");

  Createlandmine();

  Print_arr();

지뢰를 생성하는 구문입니다. 여기서 지뢰는 숫자 0을 의미합니다. 지뢰가 생성되고 나면 결과값을 보기 위해 Print_arr함수를 호출하였습니다. 참고로 함수에 인자값이 없는 이유는 지뢰가 담긴 배열이 전역변수로 선언되었기 때문에 인자로 받을 값이 없기 때문입니다.

- printf("지뢰에 맞게 숫자값 변경\n\n");

  Createlandmine2();

  Print_arr();

생성된 지뢰를 이용하여 주변 타일들의 숫자를 바꾸는 함수입니다. 자신 주변의 타일들중 지뢰 개수가 있는 만큼 자기 타일의 값을 지정해줍니다.

이제 함수들을 살펴봅시다.

- void Createlandmine(void)

  int i,j,count=0;

반복문에 쓰일 i,j변수와 지뢰 개수를 저장할 변수 count를 선언하였습니다.

- while(count != 10)

count가 10이 아니면 반복합니다. 즉 만들어진 지뢰가 10개가 아니면 반복합니다.

- count=0;

count를 0으로 만들어줍니다. 만들어진 지뢰가 10개가 되지 않을 경우 다시 만들기 위해 count를 초기화합니다.

- for(i=0;i<9;i++)

  for(j=0;j<9;j++)

2차원 배열을 조작하기 위해 2중 for문을 돌립니다.

- arr[i][j] = rand()%8;

arr[i][j]에 랜덤값을 집어넣습니다. 이 때 rand함수가 사용되었는데 이 함수를 살펴봅시다.

설명에 따르면 이 함수는 0에서 32767사이에서 랜덤한 정수를 반환한다고 나와 있습니다. 그런데 우리가 원하는 것은 랜덤으로 나오는 지뢰 즉 숫자 0만 필요합니다. 그렇다고 그냥 rand함수를 쓰자니 0이 나올 확률이 매우 적어집니다. 1/32768 확률이니까요. 

초급 지뢰찾기에서는 지뢰는 10개, 타일은 총 81개 입니다. 즉 확률은 약 1/8입니다. 그러므로 우리는 확률을 맞춰주기 위해 rand함수의 결과값에 %8를 붙여 0~7사이의 값만 나오게 하였습니다. 

- if(arr[i][j] == 0)

  count++;

랜덤으로 생성된 값이 0일 경우 count를 하나 증가시킵니다. 즉 지뢰가 생성되었을 경우 지뢰 개수를 의미하는 count를 증가시킨다는 뜻이지요.

이렇게 모든 for문을 다 실행하고 나면 arr에는 각 인덱스에 0~7값이 들어가게 됩니다. 그런데 지뢰를 의미하는 0 값이 10개를 넘거나, 10개가 안될 수 있으므로 while문을 이용하여 지뢰가 딱 10개만 생성되도록 해주었습니다.

자 다음은 이렇게 생성된 지뢰를 이용하여 주변 타일들의 값을 정하는 함수를 설명하겠습니다.

- void Createlandmine2(void)

  int i,j,count;

for문에 사용할 i,j변수와 주변 타일에 존재하는 지뢰 개수를 저장할 count를 선언하였습니다. 이 count의 값에 따라 타일의 숫자값이 바뀝니다.

- for(i=0;i<9;i++)

  for(j=0;j<9;j++)

arr을 조작하기 위해 2중 for문을 돌립니다.

- count = 0;

하나의 타일의 값 지정이 끝나면 당연히 초기화를 시켜주어야 합니다. 뒤쪽에서 처리를 해도 되지만 count를 선언할 때 초기화를 시켜주지 않았기 때문에 앞쪽에서 초기화를 하였습니다.

- if(arr[i][j] != 0)

타일의 값이 지뢰인지 아닌지를 검사합니다. 지뢰이면 if문을 건너뛰고 다음 타일을 검사합니다. 지뢰가 아니라면 값을 지정해주기 위해 if문을 실행합니다.

- if(i == 0)

i가 0일 경우입니다. 여기서 i는 행을 의미하지요.  즉 첫번째 행일 경우를 의미합니다. 첫번째 행일 경우 if문을 실행합니다.

- if(j == 0)

if(arr[i][j+1] == 0)

count++;

if(arr[i+1][j+1] == 0)

count++;

if(arr[i+1][j] == 0)

count++;

j가 0일 경우입니다. 여기서 j는 열을 의미하지요.  즉 첫번째 열일 경우를 의미합니다. 이 if문은  if(i == 0)안에 포함되어 있으므로 첫번째 행의 첫번 째 열 일 경우 if문을 실행하라는 뜻입니다.

그 다음 코드는 주변 타일이 지뢰인지 아닌지를 검사하는 코드입니다. 현재 i는 0이고 j도 0이므로 이 arr]i][j]는 첫번째 줄의 첫번째 열을 의미합니다. 이 타일은 검사할 수 있는 주변 타일이 우측타일, 우측하단타일, 하단타일 이 세개의 타일밖에 없으므로 이 세개의 타일만 검사하도록 하였습니다. 검사를 하여 이 타일들 중에 지뢰를 가진 타일이 나오면 count를 하나 증가시키도록 하였습니다.

- else if(j == 8)

if(arr[i+1][j] == 0)

count++;

if(arr[i+1][j-1] == 0)

count++;

if(arr[i][j-1] == 0)

count++;

j가 8일 경우의 타일 검사 처리부분입니다. i가 0이고 j는 8이므로 첫번째 줄의 마지막 열을 의미합니다. 여기서는 검사할 수 있는 주변 타일이 하단타일, 좌측하단타일, 좌측타일 밖에 없으므로 이 세개의 타일만 검사하여 지뢰일 경우 count를 증가시키도록 하였습니다.

- else

if(arr[i][j+1] == 0)

count++;

if(arr[i+1][j+1] == 0)

count++;

if(arr[i+1][j] == 0)

count++;

if(arr[i+1][j-1] == 0)

count++;

if(arr[i][j-1] == 0)

count++;

 j가 0도 아니고 8도 아닌 값일 때의 타일 검사 처리입니다. 즉 첫번 째 줄의 중간 열(1~7)을 의미합니다. 여기서 검사할 수 있는 주변 타일은 우측타일, 우측 하단 타일, 하단타일, 좌측 하단 타일, 좌측타일 입니다. 

- else if(i == 8)

if(j == 0)

if(arr[i-1][j] == 0)

count++;

if(arr[i-1][j+1] == 0)

count++;

if(arr[i][j+1] == 0)

count++;

이번에는 i가 8일 때의 처리입니다. 즉 마지막 줄을 의미하지요. 마지막 줄에서 j가 0일 때 if문을 실행하므로 마지막 줄의 첫번째 열이라고 보시면 됩니다. 여기서 검사할 수 있는 주변 타일은 상단타일, 상단우측타일, 우측타일 입니다.

- else if(j == 8)

if(arr[i-1][j] == 0)

count++;

if(arr[i-1][j-1] == 0)

count++;

if(arr[i][j-1] == 0)

count++;

이번에는 i가 8이고 j가 8일 때의 처리입니다. 즉 마지막 줄의 마지막 열을 의미하지요. 여기서 검사할 수 있는 주변 타일은 상단타일, 상단좌측타일, 좌측타일 입니다.

- else

if(arr[i][j+1] == 0)

count++;

if(arr[i-1][j+1] == 0)

count++;

if(arr[i-1][j] == 0)

count++;

if(arr[i-1][j-1] == 0)

count++;

if(arr[i][j-1] == 0)

count++;

이번에는 i가 8이고 j가 1~7일 때의 처리입니다. 즉 마지막 줄의 중간열(1~7)을 의미합니다. 여기서 검사할 수 있는 주변 타일은 우측타일, 상단우측타일, 상단타일, 상단좌측타일, 좌측타일 입니다.

else

if(j == 0)

if(arr[i-1][j] == 0)

count++;

if(arr[i-1][j+1] == 0)

count++;

if(arr[i][j+1] == 0)

count++;

if(arr[i+1][j+1] == 0)

count++;

if(arr[i+1][j] == 0)

count++;

이번에는 i가 1~7이고 j가 0일 때의 처리입니다. 즉 중간 줄(1~7)의 첫번째 열을 의미합니다. 여기서 검사할 수 있는 주변 타일은 상단타일, 상단우측타일, 우측타일, 하단우측타일, 하단타일입니다.

- else if(j == 8)

if(arr[i-1][j] == 0)

count++;

if(arr[i-1][j-1] == 0)

count++;

if(arr[i][j-1] == 0)

count++;

if(arr[i+1][j-1] == 0)

count++;

if(arr[i+1][j] == 0)

count++;

i가 1~7이고 j가 8일 때의 처리이므로 중간 줄의 마지막 열을 의미합니다. 여기서 검사할 수 있는 주변 타일은 상단타일, 상단좌측타일, 좌측타일, 하단좌측타일, 하단타일 입니다.

- else

if(arr[i-1][j-1] == 0)

count++;

if(arr[i-1][j] == 0)

count++;

if(arr[i-1][j+1] == 0)

count++;

if(arr[i][j+1] == 0)

count++;

if(arr[i][j-1] == 0)

count++;

if(arr[i+1][j-1] == 0)

count++;

if(arr[i+1][j] == 0)

count++;

if(arr[i+1][j+1] == 0)

count++;

i가 1~7이고 j가 1~7일 때의 처리입니다. 여기서는 주변 타일을 전부 검사할 수 있는 위치이지요. 그렇기에 자신의 타일을 둘러싸고있는 8개의 타일들을 검사합니다. 

코드가 조금 길지만 알고리즘 자체는 간단한 편입니다. 이제 count를 이용하여 타일의 값을 정해주는 부분을 살펴봅시다.

- switch(count)

case 1:

arr[i][j] = 1;

break;

case 2:

arr[i][j] = 2;

break;

case 3:

arr[i][j] = 3;

break;

case 4:

arr[i][j] = 4;

break;

case 5:

arr[i][j] = 5;

break;

case 6:

arr[i][j] = 6;

break;

case 7:

arr[i][j] = 7;

break;

case 8:

arr[i][j] = 8;

break;

default:

arr[i][j] = 9;

break;

count의 값, 즉 주변에 있는 지뢰 개수에 따라 타일의 값을 지정해주는 부분입니다. 지뢰가 하나 있을 경우 값을 1, 2개가 있으면 2,.... 이런식으로 8까지 값을 저장합니다.

여기서 하나 아셔야 할것은 default인데 이 부분은 count값이 1~8이 아니라는 것을 의미합니다. 즉 지뢰가 하나도 없다는 것을 의미하지요. 그러므로 case 0으로 처리할 수도 있습니다. 아무튼 지뢰가 하나도 없을 경우에는 값을 9로 설정합니다. 이 9라는 값은 빈타일을 의미합니다. 

위와 같은 타일을 뜻하는 것이지요. 이렇게 최종적으로 한 타일의 값을 지정해주고 나면 count값은 for문을 다시 시작할 때 초기화됩니다.

자 이제 남은 것은 타일을 출력하는 함수 Print_arr인데 이것은 간단하니 생략하겠습니다. 자 이제 결과를 한 번 봅시다.

위의 배열은 지뢰를 생성한 배열입니다. 지뢰를 의미하는 0이 무작위로 10개 생성된 것을 확인할 수 있습니다.

밑의 배열은 지뢰값을 이용하여 주변타일들의 값을 바꿔준 배열입니다. 제대로 배치가 되었는지 한번 확인해보셔도 좋습니다.

다시 실행하면 랜덤으로 바뀌는 것을 확인할 수 있습니다.

같은 원리로 중급, 고급 난이도의 지뢰도 구현할 수 있습니다.

네 이것으로 포스팅을 마치겠습니다.

Posted by englishmath
,

안녕하십니까. 이번에는 c언어로 지뢰찾기를 만드는 것을 포스팅하겠습니다. 물론 실력이 부족하여 100퍼 똑같이 만들어내기는 힘들어서 최대한 비슷하게 만들어보았습니다. 모델은 windows7 지뢰찾기 입니다.

일단 제가 만든 지뢰찾기는 기존의 지뢰찾기와는 많이 다릅니다. 기존의 지뢰찾기는 소리, 애니메이션 등의 이벤트가 추가되어있으며 난이도설정, 다시시작 등의 여러 기능이 있지만 제가 만든 지뢰찾기는 최소한의 기능만 넣었습니다. 이러한 점은 나중에 차차 기능을 추가하겠습니다.

먼저 완성된 지뢰찾기 화면을 보여드리겠습니다.


다음 포스팅에서 본격적으로 시작하겠습니다.

Posted by englishmath
,

안녕하십니까. 저번에 작성했던 소스를 추가작성하여 프로그램을 동작시켜 봅시다.

먼저 소스를 보겠습니다.

아는 분의 조언으로 글꼴을 바꿨습니다. 혹시 원하는 글꼴이 있으시다면 바꿔드리겠습니다.

프로그램 동작을 위해 소스코드를 수정한 것이므로 WinMain에서는 수정한 것이 없습니다. WndProc함수를 봅시다.

- TCHAR regcodeStr[50]

사용자가 입력한 값을 저장시키기 위한 배열을 선언하였습니다.

- int Message_result

메세지박스로 인해 호출된 메세지의 결과값을 저장하기 위해 선언하였습니다.

WM_COMMAND부분을 봅시다.

- case IDM_PROGRAMM_EXIT1:

PostQuitMessage(0);

break;

이 IDM_PROGRAMM_EXIT1은 리소스 메뉴를 의미합니다. 이 상수값은 리소스 헤더파일에 정의되어 있습니다. 

좀 더 자세히 알아보기 위해 ResEdit로 살펴봅시다.

IDM_PROGRAMM_EXIT1는 Programm의 Exit를 의미하는군요.

즉 case IDM_PROGRAMM_EXIT1: 는 Exit를 눌렀을 때의 동작을 뜻하는 것을 알 수 있습니다.

그다음 밑의 코드내용을 보면 PostQuitMessage함수를 호출한다고 되어있습니다. 즉 프로그램을 종료시키라는 뜻입니다. 간단하지요?

- case IDM__ER1:

MessageBox(hWnd,L"VB5-CrackMe 1.0 by hackingboy [DCD]",

L"VB5-Crackme 1.0",MB_OK);

break;

IDM__ER1을 살펴봅시다.

?er의 ID라는 것을 알 수 있습니다. 원본에선 ?er를 누르면 메세지박스가 띄워집니다. 그래서 여기에서도 똑같이 띄웠습니다. 한 가지 주의하실 점이 있다면 메세지박스의 첫번째 인자에 hWnd를 넣어주어야 합니다. 만약 이 값이 NULL로 지정되어있으면 메세지박스가 중복으로 생성되기 때문입니다.

- case REGISTRIEREN:

Registrieren 버튼을 눌렀을 때의 동작입니다.

- GetWindowText(regcode,regcodeStr,11);

GetWindowText함수로 regcode에 적힌 문자를 11개만큼 가져와서 regcodeStr에 저장시킵니다. 가져오는 문자열에는 널값이 포함되어 있으니 실제로 가져오는 글자 수는 10개입니다. 

- if(lstrcmp(regcodeStr,L"2G83G35Hs2") == 0)

GetWindowText함수로 인해 저장된 regcodeStr의 문자열과 2G83G35Hs2랑 같은지 안같은지를 비교합니다. 두 문자열이 같으면 lstrcmp는 0을 반환합니다.

- Message_result = MessageBox(hWnd,L"Danke, das Passwort ist richtig !",

L"Gluckwunsch !",MB_ICONWARNING | MB_OKCANCEL);

두 문자열이 같을 경우 성공메세지를 출력합니다. 여기서 MB_ICONWARNING 인자가 쓰였는데 이 인자는 느낌표 아이콘을 메세지에 넣겠다는 뜻입니다. 그리고 MB_OKCANCEL을 인자로 주어 확인, 취소 버튼을 메세지에 넣었습니다. 어떤 버튼을 누르냐에 따라 반환값이 달라지므로 Message_result에 반환값을 넣었습니다.

- if(Message_result != 1)

PostQuitMessage(0);

Message_result의 값이 1이 아니면 프로그램을 종료합니다. MessageBox에서 확인 버튼을 누르면 반환값은 1이 됩니다. 즉 위의 if문은 확인버튼을 누르지 않았을 시 프로그램을 종료하라는 코드입니다. 실제로 오리지널 03.exe에서는 성공문자열에서 취소버튼을 누르면 프로그램을 종료합니다.

- else

MessageBox(hWnd,L"Error ! Das Passwort ist falsch !",

L"PASSWORT FALSCH !",MB_ICONERROR | MB_OK);

입력한 값이 정답과 다를 경우 실패 문자열을 출력합니다. MB_ICONERROR 인자는 X표 아이콘을 의미합니다.

- case Exit:

PostQuitMessage(0);

break;

Exit버튼을 눌렀을 때의 동작입니다. 당연히 종료하도록 코드를 작성하였습니다.

생각보다 간단하지요? 프로그램을 동작시켜 봅시다.

네 이것으로 포스팅을 마치겠습니다.

 


Posted by englishmath
,

안녕하세요. 이번에는 우리가 코드엔진에서 풀어본 문제 중 하나인 bagic의 03문제인 프로그램을 만들어보겠습니다. 일단 동작을 봅시다.

실행시 메세지박스가 하나 띄워집니다. 확인과 취소 버튼이 있는데 확인 버튼을 누른시 아래와 같은 창이 뜹니다. 반대로 취소 버튼을 누르면 그냥 종료가 되버립니다.

동작을 확인했으니 일단 겉만 만들어봅시다. 제가 작성한 소스를 보여드리겠습니다.

저번 포스팅에서 왠만한 api함수들은 전부 설명드렸으니 새로 나온 함수 혹은 코드만 설명하겠습니다.

- #include "resource.h"

리소스가 선언되어 있는 헤더파일을 불러옵니다. 이것은 나중에 설명드리겠습니다.

- #define REGCODE 0

  #define REGISTRIEREN 1

  #define Exit 2

edit컨트롤과 버튼컨트롤을 제어하기 위해 정의된 상수입니다.

- HWND regcode;

edit컨트롤의 핸들을 저장시킬 변수를 전역변수로 선언하였습니다.

WinMain을 살펴봅시다.

- int Message_result;

MessageBox함수의 결과값을 저장시킬 변수입니다.

- HBRUSH bgBrush;

윈도우의 배경색을 저장할 용도로 HBRUSH 형의 변수를 선언하였습니다.

- bgBrush = CreateSolidBrush(RGB(238,238,238));

HBRUSH형의 변수에 CreateSolidBrush의 결과값을 집어넣습니다. CreateSolidBrush의 정의를 봅시다. MSDN을 참고합시다.

컬러를 인자로 받아 HBRUSH 형태로 반환합니다. 즉 이 함수는 단색 브러쉬를 만드는 함수입니다. 윈도우의 배경색을 주기 위해 사용하였습니다.

- wndclass.hbrBackground = bgBrush;

윈도우의 배경색을 아까 만든 bgBrush로 지정합니다. 여기서는 RGB(238,238,238)로 만들었으므로 회색 비슷한 색깔로 지정됩니다.

- wndclass.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);

윈도우의 메뉴를 IDR_MENU1로 지정합니다. 이 IDR_MENU1는 리소스를 의미합니다. 이 리소스 작성법은 나중에 설명드리겠습니다.

그리고 MAKEINTRESOURCE함수가 사용되었습니다. MSDN을 참고합시다.

정수형을 받아서 문자열로 반환합니다. 즉 이 함수는 리소스번호를 받아 리소스 문자열로 반환시킵니다. 우리가 만든 메뉴 리소스인 IDR_MENU1이 헤더파일에 상수로 정의되어 있으므로 이 IDR_MENU1 번호를 문자열로 변환시켜 lpszMenuName에 넣어주었습니다.

 - wndclass.lpszClassName = L"a";

클래스 이름을 지정합니다. 여기서는 그냥 a로 주었습니다.

- Message_result = MessageBox(NULL,L"Entferne diesen Nag, oder bekomme das richtige Passwort heraus !",L"Nag Meldung",MB_ICONQUESTION | MB_OKCANCEL);

MessageBox함수를 사용하여 메세지박스를 띄웁니다. 이 메세지박스는 프로그램을 실행할 때 제일 먼저 나오는 메세지박스입니다.

여기서 눈여겨보실 부분은 마지막 인자부분인데 ,MB_ICONQUESTION | MB_OKCANCEL입니다. MB_ICONQUESTION은 질문 아이콘을 메세지박스에 띄우는 인자입니다. 즉

빨간박스의 저 물음표 아이콘을 띄운다는 뜻입니다. 

MB_OKCANCEL은 OK버튼과 CANCEL버튼을 만든다는 뜻입니다. MessageBox함수는 각각의 버튼을 눌렀을 때 반환되는 결과값이 다릅니다.

- if(Message_result == 1)

Message_result는 MessageBox함수의 결과값이죠. 즉 어떤 버튼을 눌렀느냐에 따라 if문을 실행하냐 안하냐를 결정합니다. 결과값이 1이면 확인버튼을 눌렀다는 뜻이므로 조건문이 참이 되어 if문 안의 코드를 실행합니다. 만약 취소를 눌렀다면 if문을 실행하지 않고 else문을 실행합니다.

- hwnd = CreateWindow(L"a",L"DCD VB5-CrackMe 1.0",WS_SYSMENU | WS_VISIBLE,73,73,253,181,NULL,NULL,hInstance,NULL);

윈도우를 생성합니다. 여기서는 윈도우 스타일에 WS_SYSMENU 인자를 주었습니다. 이 인자는 닫기 창만 존재하는 윈도우를 의미합니다. 덧붙여서 이 스타일이 적용된 윈도우는 크기를 바꿀 수 없습니다.

- else

return 0

메세지박스의 결과값이 1이 아닐 경우 프로그램을 종료합니다.

나머지 소스코드는 앞시간에 전부 설명드렸으니 따로 설명드리진 않겠습니다.

자 이제는 리소스를 만들어봅시다. 리소스 파일부분에서 rc파일을 만듭니다.

이제 만들었으니 편집을 할 차례입니다. 저는 resedit를 사용하겠습니다.

아 그리고 앞 포스팅에서 깜박하고 빼먹은 부분이 있는데 resedit를 사용하시는 분들은 resedit의 경로를 따로 지정해주어야 합니다.

Options의 Preferences...를 누릅시다.

General의 경로를 추가해주는데 어떤 경로로 설정하냐면 Windows.h 파일이 들어있는 경로로 설정해주면 됩니다.

경로를 지정해주었으면 아까 만든 리소스 파일을 열어봅시다. 보통 리소스파일은 생성시 소스파일과 동일한 위치에 생성됩니다.

해당 rc파일을 성공적으로 여셨다면 메뉴 리소스를 추가합시다.

IDR_MENU1이라는 리소스가 생성되었습니다. 우측에 보시면 Type here이 보이시죠? 이 부분이 바로 윈도우의 메뉴부분입니다. 03.exe에 맞게 직접 메뉴를 지정해줍시다.

지정을 다하시고 나서 해당 메뉴를 누르면 밑에 하위항목이 보입니다. 03.exe에는 하위항목이 Exit밖에 없으므로 Exit를 적어줍시다.

메뉴가 다 작성되었습니다. 이제 메뉴의 설정을 변경해봅시다.

메뉴 ?er을 누르고 속성부분에서 Popup부분을 False로 설정해줍니다. 이 Popup 부분은 이 메뉴가 하위항목이 있냐 없냐를 설정해주는 부분입니다. Programm 메뉴는 하위항목 Exit가 있기 때문에 이 부분을 True로 설정해주어야 하지만 ?er은 하위항목을 표시하지 않기 때문에 이 Popup부분을 False로 고쳐주어야 합니다.

여기까지가 메뉴였습니다. 이번에는 아이콘을 추가합시다. 아이콘 추출법과 추가하는 방법은 앞시간에 올렸으므로 따로 설명드리지 않겠습니다.

다 만들었으면 저장해줍시다. 그리고 비주얼 스투디오로 돌아와 리소스의 헤더파일을 추가합시다.

resource.h 파일을 찾아 추가해주시면 됩니다. 이 헤더파일 안에는 우리가 만든 리소스들이 상수로 정의되어있습니다. 이렇게 추가된 resource.h 파일을 사용하기 위해서는 소스코드이 위에 include "resource.h" 를 추가해주어야 합니다. 

여기까지 다되셨으면 우리가 만든 리소스를 사용하기 위해 소스코드를 약간 수정해줍시다. 소스코드에서는 메뉴가 추가되어있지만 아이콘을 따로 추가가 되어있지 않습니다. 그러므로 아이콘을 새로 추가해 줍시다. 다음과 같이 수정합시다.

 - wndclass.hIcon = LoadIcon(NULL,IDI_APPLICATION); -> 기존코드

 - wndclass.hIcon = LoadIcon(hInstance,MAKEINTRESOURCE(IDI_ICON1));

-> 수정코드

LoadIcon의 인자를 변경해줍니다. 기존에 정의되어 있는 아이콘을 사용할 경우에는 첫 인자 값을 NULL로 주지만 그 외의 아이콘을 사용할 경우에는 첫 인자에 hInstance를 줍니다. 두번째 인자는 우리가 만든 아이콘을 문자열 형태로 넣어주시면 됩니다.

자 이제 코드 설명이 전부 끝났습니다. 한번 실행을 시켜 봅시다.

그럴듯하게 만들어졌습니다. 다음포스팅에서는 동작을 구현해봅시다.

이것으로 포스팅을 마치겠습니다.

Posted by englishmath
,