티스토리 뷰

이번에는 화투 이미지를 이용해 짝을 맞추는 게임을 만들어 보려 한다.

화투의 이미지는 뒷면 1장, 앞면 18장으로 총 19장으로 구성되어 있다.


대화상자형식으로 프로젝트를 만든 후 우선 헤더파일로 가서 카드 이미지를 불러오기 위해 CImage로 선언을 한다. 이 클래스는 외부 이미지를 불러와 출력할 때 사용되는 것이다. 


private :

CImage m_card_image[19];


일반적으로 윈도우가 만들어질 때, 운영체제가 wm_creat 메시지를 준다. 따라서 만들고 바로 쓰지 말고 wm_creat 메시지를 받은 뒤 써야한다. 하지만 대화상자는 wm_creat 가 발생하는 때가 자기 자시만 만들어졌을 때 이고, 리소스를 포함한 모든게 만들어질때, wm_initDialo가 발생한다. 따라서 대화상자는 이 메시지가 발생한 뒤 사용해야하고, creat메시지를 사용하면 안된다. 반대로 윈도우도 init 메시지를 사용해선 안된다. 


on_initDialog 클래스는 wm_initDialog 메시지를 처리하는 함수이다. 여기서 작업하는 것은 대화상자가 생기기 전으로 눈에 보이지 않는다. 이 함수가 끝나고 나면 대화상자가 나타난다. 여기서 대화상자의 크기를 바꾸거나 위치를 바꾸는 것은 불가능한데, init 다음에 생성되는 것이 있었고 거기서 설정이 또 바뀌기 때문이다. 


이미지를 불러오기 위해 on_initDialog 함수에 소스를 치면된다.


Cstring str;


for(int i = 0; i < 19; i++) {

str.Format(L"c:\\temp\\%03d.bmp", i);

m_card_image[i].Load(str);

}


CString 이 클래스는 MFC에서 제공하는 클래스로 역할은 C언어에서 찾아볼수 있다. sprintf라는 함수가 화면으로 문자열을 내보내는 것이 아닌 프로그램으로 보내는 역할을 한다. 이와 같이 CString도 같은 역할을 하지만 느리기 때문에 프로그래밍을 배운 초반에만 사용하고 나중에는 다른 함수로 대처하는 것이 좋다. 내부적으로는 포인터로 구성되어있지만, 배열처럼 사용 가능하다.


C++은 연산자 오버로딩으로 통해 연산자 명도 함수 이름으로 사용이 가능하다. 연산자 오버로딩을 통해 연산자도 재정의가 가능하며, 연산자 오버로딩을 가지고 있으면 연산자 오버로딩을 따로 써주지 않아도 적용된다.


str.Format(L"c:\\temp\\%03d.bmp", i);

화면으로 내보내는 것이 아니라 CString 클래스가 문자열을 가진다.


m_card_image[i].Load(str);

이미지를 가져오기 위해 Load 클래스를 사용한다.



카드 이미지를 띄우기 위해 OnPaint를 수정해준다.


else{

CPaintDC dc(this);

for(int i =0; i < 19; i ++){

m_card_image[i].Draw(dc, i * 36, 0);

}

}


m_card_image[i].Draw(dc, i * 36, 0);

가져온 이미지를 화면에 그리기 위해서 Draw 클래스를 사용해 준다.


여기까지 한 결과를 실행해보면 아래와 같은 화면이 나오게 된다.



이제 이 카드들을 이용해 카드 맞추기 게임을 하려 하는데, 가로 X 세로 6개씩 카드를 랜덤하게 썪기 위해 우선 36개의 배열을 만든다.


private :

CImage m_card_image[19];

char m_card_table[36];


현재 사용하는 카드 18장을 2장씩 나오게 해 같은 그림 2장을 맞추는 게임이다. 따라서 36개의 배열을 만들고 그 배열에 인덱스 0 ~ 17을 두개 넣어 비교하는 방식으로 진행할 것이다.


//on_initDialog 함수에 넣으면 된다.

for(int i = 0; i < 18; i++){

m_card_table[i] = i % 18 + 1;

}


6 X 6 형식으로 출력하기 위해 OnPaint로 가서 소스를 수정해준다.


else{

CPaintDC dc(this);

for(int i =0; i < 19; i ++){

m_card_image[i].Draw(dc, i * 36, 0);

}

}


여기서


else{

CPaintDC dc(this);

char index;

for(int i =0; i < 36; i ++){

index = m_card_table[i]; 

m_card_image[index].Draw(dc, (i % 6) * 36, (i / 6) * 56);

}

}


출력되는 카드가 6개씩 일렬로 나열되게 하기 위해서 나눗셈과 나머지 연산으로 조정을 해준다.

그림의 크기가 가로 36, 세로 56이므로, 배열이 36만큼 돌때, 가로에 6개의 카드를 놓기 위해 %6을 해준다.

그러면 0 ~ 5 까지는 나머지가 0 ~ 5 사이의 값이 나오고,  6 ~ 11 까지도 나머지 역시 0 ~ 5 사이의 값이 나온다.

이때 36을 곱해주면 크기가 카드 크기에 알맞게 증가한다. 반대로 세로의 경우 /6을 해주면 0 ~ 5까지는 몫이 0,

6 ~ 11까지 몫이 1이 되는데, 여기에 *56을 해주면 세로의 크기 만큼 위치하게 된다.



아래와 같은 결과가 나오게 된다.




이제 카드를 랜덤하게 섞으려고 한다. 랜덤하게 섞기 위해 난수 발생함수인 rand를 이용해 준다. rand를 쓰려면 srand를 통해 난수의 기준을 정해줘야 한다. 난수는 타임시드로 결정이 난다. 타임시드를 랜덤으로 바꿔가면서 해야한다. 


먼저 타임 시드를 설정해 준다.


srand(( unsigned int ) time(NULL) );

for(int i =0; i > 50; i++ ){

rand();

}


그리고 카드를 두장 뽑아 섞어야 하므로 변수를 선언해 준다.


char first, second, temp;


카드를 섞는 반복문을 설정해준다.


for(int i =0; i > 50; i++ ){

first = rand() % 36;

second = rand() % 36;


if(first != second){

temp = m_card_table[first];

m_card_table[first] = m_card_table[second];

m_card_table[second] = temp;

}

}


이제 앞면을 잠깐 보여주고 뒷면으로 뒤집기 위해서 타이머를 사용한다.


SetTimer( 1, 3000, NULL);


함수를 사용하면 wm_timer 메시지가 발생하여 어떤 타이머에 의해 발생하는지 구분하기 위해 아이디가 필요한다.

처음 인자인 1의 의미가 어떤 타이머 인지 구분해주는 아이디이다.


이제 클래스 마법사에서 타이머를 추가해 준다. 추가 방법은 wm_timer을 검색후, OnTimer를 추가해준다.


void CFlowerGameDlg :: OnTimer (UNIT_PTR nlDEvent){


CdialogEx:: OnTimer(nlDEvent);


}


wm_timer 메시지가 발생시 실행되는 함수로, 타이머 아이디 값이 인자로 넘어온다. 



앞면을 보여주고 3초뒤 뒷면을 보여주도록 하려면 앞면인지 뒷면인지 상태를 보여주는 변수 하나를 추가뒤, 

타이머에서 3초뒤 화면을 갱신하고 wm_paint에서 조건문을 통해 뒷면인 경우를 설정해주면 된다.


private :

CImage m_card_image[19];

char m_card_table[36];

char m_show_flag = 1; // 1이면 앞면 0이면 뒷면을 의미한다.


void CFlowerGameDlg :: OnTimer (UNIT_PTR nlDEvent){


if(nlDEvent == 1){

KillTimer(1);

m_show_flag = 0;

Invalidate();


CdialogEx:: OnTimer(nlDEvent);

}


else{

CPaintDC dc(this);

char index;

for(int i =0; i < 36; i ++){

if(m_card_table[i] >0){

if(m_show_flag == 1) index = m_card_table[i]; 

m_card_image[index].Draw(dc, (i % 6) * 36, (i / 6) * 56);

}

}

}


여기서 알아봐야할 것이 있다. Invalidate(); 함수인데, 이 클래스는 현재 윈도우를 무효화하는 함수이다. 

invalide -> valide 상태로 바뀌면 wm_paint가 발생되기 때문이다. wm_paint 가 발생되고, OnPaint함수 에서 else문이 실행된다.


이제 두장의 카드를 선택시 같으면 사라지고 다르면 뒤집어 지도록 해보자.

마우스 클릭시 조건을 설정하기 위해 LBUTTONDOWN을 클래스 마법사로 추가해보자.

우선 헤더 파일로 가서 선택할 카드 인덱스에 대한 변수를 추가하자.


private :

CImage m_card_image[19];

char m_card_table[36];

char m_show_flag = 1; // 1이면 앞면 0이면 뒷면을 의미한다.

char m_first_card_index = -1 ; //아직 카드가 선택이 되지 않았다고 알리기 위해 -1값을 넣어 준다.


이제 OnLButtonDown내에 조건을 설정해 준다.


void CFGameDlg::OnLButtonDown(UINT nFlags, CPoint point)

{

if (m_show_flag) return;   //앞 면일때 클릭할 수 없도록 막는 코드 0일때는 flase이므로 내려가게 된다.

                                             //0을 제외한 나머지 값들은 true


int x = point.x / 36, y = point.y / 56;

if (x < 6 && y < 6) {

char pos = y * 6 + x;   //내가 선택한 테이블의 인덱스이다.

if (m_card_table[pos] == 0) return;   //찾은 카드를 클릭할 수 없도록 막는 코드


CClientDC dc(this);

char index = m_card_table[pos];

m_card_image[index].Draw(dc, x * 36, y * 56);


if (m_first_card_index == -1) {   //아직 선택이 되지 않았다.

m_first_card_index = pos;

}

else {

if(m_first_card_index != pos) {

if (m_card_table[m_first_card_index] == m_card_table[pos]) {

m_card_table[m_first_card_index] = 0;

m_card_table[pos] = 0;

}

else m_show_flag = 2;

m_first_card_index = -1;

}

SetTimer(1, 1000, NULL);         

}      

}

CDialogEx::OnLButtonDown(nFlags, point);

}


프로그램을 실행하면 초기화면에서 앞면을 보여주고 3초뒤에 뒤집어진다.




그림을 맞추게 되면 아래와 같이 맞춰진 그림들은 사라지게 된다.





댓글