심화반 예제 (테트리스)

모르는 사람이 없는 테스리스 게임 소스코드, 설치파일을 소개합니다.

테트리스 게임은 1984년 구 소련의 프로그래머 알렉세이 퍼지노프가 만든 게임이라고 합니다.

C++로 만든 과정은 아래와 같습니다.

1. 테트리스 블록들(총 7가지 모양)을 2차원 배열(4x4)로 만든 후 각자의 모양을 bool 타입의 변수에 저장합니다.

2. 테트리스 맵이 될 2차원 배열을 만들어 (가로 20, 세로 10) 각 셀의 배열의 위치 정보를 저장해 둡니다.

3. 프로그램 시작시 랜덤함수를 이용해 떨어질 도형 모양의 번호를 구한 후, 각 도형모양에 맞는 블록을 맵의 가장 위쪽 좌표에 그려줍니다.

4. 한단계식 아래로 이동시킵니다. (Y축의 배열 인덱스를 증가)

5. 키보드 방향키 (좌, 우)를 누르면, 떨어지는 도형의 배열을 회전 시켜 다시 그립니다.

6. 맨 아래 바닥에 닿았거나 또는 블록에 닿을때까지 계속 아래로 이동하다가 해당 조건을 만족하면 도형의 위치정보를 맵에 기록해 둡니다.

7. 3번 ~ 7번 과정을 반복합니다.

8. 만약 블록이 안착(?) 했다면 한 줄(행)이 모두 도형으로 채워졌는지 검사하고, 맞다면 해당 줄의 도형정보를 삭제한 후, 위에 쌓여진 도형들은 6번의 과정을 거쳐 아래로 내려줍니다.

실제 코드로 구현한 클래스 다이어 그램입니다.



좀 더 자세한 내용은 링크된 소스코드를 참조해서, 한번 만들어 보시기 바랍니다.



생각보다 어렵지만, 테스리스 역시 프로그래밍 공부하기 좋은 예제입니다.



버그가 있다면 알려주세요. ^^

  • 개발환경 : Microsoft Visual Studio 2015, Windows 10 Pro 64bit
  • 개발언어 : C++, MFC
  • 핵심 적용 기술, 환경 
  1. Device Context, GDI, Raster Operation, Double Buffering (깜빡임방지)
  2. SDI (Single Document Interface)
  3. Windows Message (Keyboard, Command Message)
  4. Thread
  5. Class Inheritance (클래스 상속)
  6. Media Control Interface (소리재생을 위해)

댓글

  1. 기존 테트리스와 다르게 맨 밑에 줄을 완성시키는 것 말고 그 위에 줄을 완성시켰을때 그 줄이 삭제가 되면서 블럭이 유지가 안되고 다 떨어져서 그대로 줄을 완성하는 버그가 있는것 같습니다

    답글삭제
    답글
    1. 안녕하세요.
      버그 리포트 감사합니다. ^^

      오랜만에 코드를 다운받아 해보니, 윗줄에 블럭이 완성되면 아래로 남은 블럭이 떨어지게 코드를 만들어 두었습니다. (아래에 블럭이 없다면)

      이부분은 블록이 공중부양되는게 어색해 의도적으로 그렇게 만들지 않았나 생각이 듭니다.

      그 과정에서 떨어지는 블럭들이 맞추어 지며 삭제가 일어나는 모습(착착착 삭제)이 지연이 없어 순식간에 보여지게 되네요.

      삭제
    2. 답변 감사합니다.
      그런데 한줄이 완성되어 삭제될 때 위에 있는 블럭들이 아래블럭이나 바닥에 닿을 때까지 내려오게하지 않고 한줄이 완성되어 삭제될때 그 위에 있는 블럭들이 모양을 지키면서 지워진 한줄에 대해서만 내려오게 하려면 2차원배열 저장 반복문에서 어떻게 코드를 수정해야 할까요? CheckLine() 함수에서 bDel 쪽에서 코드를 어떻게 수정해야할 지 잘 모르겠습니다.

      삭제
    3. 원작에서는 한줄 완성시 삭제된 블럭위 블럭들이 바닥이나 아래 블럭에 닿기전까지 계속 내려오지 않나요?

      질문하신 대로 완성된 줄 위 블럭들이 아래로 한칸만 내려오면 매우 어색할것 같지만, 아래와 같이 가능합니다.

      CheckLine() 함수에서

      1. 한줄이 완성되었을 때, 해당 줄의 row 행을 기억하도록 합니다. (변수 추가)

      2. bDel이 참인 경우, for 문에서 완성된 행(row)보다 위에있는 모든 블럭만큼 반복하도록 하고, 예를 들면 for (int row=완성줄; row >= 0; row--)

      3. 해당 블럭이 있다면 row를 +1해주면 한칸만 내려오게 됩니다. 그런 다음 원래 블럭은 다시 false로 만들어 없애야 합니다.

      m_board[row+1][col].bExist = true;
      m_board[row][col].bExist = false;

      삭제
    4. 만약 삭제된 행의 윗 블럭들만 아래로 계속 이동시킨다면 bDel이 참인 경우 for문만 변경하면 됩니다.

      for (int row = 삭제된 행; row >= 0; row--)

      삭제
    5. 어 제가 이 테트리스도 해보고 다른 테트리스 게임을 해보니까 느낀건데요.
      테트리스는 한줄이 완성 되었을때 그 줄이 없어지면서 그 줄 위에 블럭들이 내려오는 것은 동일한테요. 그런데 블럭의 모양이 유지가 되지않은 상태로 다 바닥에 떨어집니다. 그러니까 한줄 위에 여러가지 모양을 쌓았을때 그 모양들이 쌓인 모습 그대로 바닥에 떨어지는 것이 아니라 블럭들이 무너져 내리면서 밑에 떨어지는 것이 기존 테트리스와 다른 부분 인것 같습니다. 그래서 분명 한 줄만 완성시켜 한줄을 없애도 그 위에 블럭들이 무너져내리면서 보드를 채우니까 줄이 여러개 사라지는 현상이 있는 것 같습니다

      삭제
    6. 제가 이상하다고 생각하는 부분을 조금 더 설명을 드리자면 한 줄이 완성되어 없어질 때 기존에 쌓여있던 블럭들이 전부 무너지는 것 같습니다.
      그 완성된 한줄만 딱 없어지고 전체적인 모양들은 유지가 되어야 하는데 모양유지가 안되고 한줄이 완성되면 다른 줄들도 완성되어 없어지는 부분이 기존 테트리스와 다른 것 같습니다.

      항상 친절한 답변 감사합니다 공부에 많은 도움이 되고 있어요 :)

      삭제
  2. 게임이 끝난뒤 게임을 다시하시겠습니까 예 아니오 부분에서 아니오를 누르면 종료 오류 생기는 버그가 있습니다

    답글삭제
    답글
    1. 이건 버그가 맞습니다.

      확인해보니, 게임이 종료창이 뜰 때 쓰레드를 종료하고 포인터를 null로 해두는데, 뷰의 소멸자에서 게임중간에 종료시 쓰레드를 종료하기 위해 다시한번 StopThread() 함수를 호출하네요.

      근데 이때 m_pThread 포인터가 이미 null이 되어 있어 생기는 문제입니다.

      StopThread() 함수에 아래 코드를 추가하여 보완하였습니다.

      if (m_pThread == NULL)
      return;

      감사합니다. ^^

      삭제
    2. m_pThread가 NULL이 되는 지점을 찾고 있었는데 그냥 저렇게 처리하면 되는군요
      감사합니다 ~ ^^

      삭제
  3. if (bDel == true) {
    int row, col, s_row;
    for (row = BOARD_ROW - 1; row >= 0; row--) {
    for (col = 0; col < BOARD_COL; col++) {
    if (m_board[row][col].bExist == true) {
    s_row = row;
    }
    }
    }
    for (row = s_row; row >= 0; row--) {
    for (col = 0; col < BOARD_COL; col++) {
    if (m_board[row][col].bExist == true) {
    int newrow = row;

    while (1) {
    if (newrow >= s_row || m_board[newrow + 1][col].bExist) {
    m_board[newrow][col].bExist = false;
    break;
    }
    else {
    newrow++;
    }

    m_board[newrow][col].bExist = true;
    m_board[row][col].bExist = false;

    }
    }
    }
    }
    벽돌이 무너져 내리는 것을 방지하기 위해 bDel이 참일때 벽돌을 지우는 곳을 바꿔 보았는데 이러니까 한줄 완성 되고 나서 그 위에 거가 안내려오고 다 그 완성된 줄 까지 삭제가 되더라고요 딱 완성된 줄만 없애고 싶은데 이 완성된 줄을 없애려면 어떻게 해야하는지 알려주실 수 있나요 ㅠㅠ

    답글삭제
    답글
    1. 완성된 줄만 없애는 것은 간단합니다.
      아래 코드처럼 bDel 이 참일때 수행되는 모든 행동을 하지 않으면 됩니다.

      if (bDel)
      {
      // 아무것도 안함
      }

      왜냐하면 bDel 일때 이미 위쪽의 for 문에서 한줄은 삭제된 상태이고, bDel일때 수행하는 것은 떠있는 블록을 내리는 행동이기 때문입니다.

      삭제

댓글 쓰기

이 블로그의 인기 게시물

Qt Designer 설치하기

PyQt5 기반 동영상 플레이어앱 만들기