본문 바로가기

C언어

[C언어] 초보자도 구현할 수 있는 테트리스 게임 만들기 ep.1 전반적인 개요(with tetris Algorith)

여러분 안녕하세요!

2021.09.10 - [C언어/프로젝트] - [C언어] C언어로 테트리스를 만들면서 느낀 점(you can do it)_

윗글은 테트리스를 만들면서 짧게 쓴 제 후기입니다.

 

이번 포스트는 C Language로 콘솔창에서 테트리스 게임을 만들기 전에 숙지하면 좋을 개념과 요구사항을 알려드리려고 합니다. 먼저 짧게 요구사항을 소개한 후에, 제가 개발하면서 느낀 경험을 바탕으로 어떻게 이런 요구사항들이 도출됬는지 실제로 제가 고민하고 매번 새롭게 직면한 문제들과 해결하기위해 필요한 개념이 무엇인지를 도출해내는 제 스토리 기반으로 풀어서 소개하려고 합니다.

 

그 전에, 제가 만든 테트리스 영상입니다: )

코드 보러가기: https://github.com/SHcommit/Tetris-game-in-C-lang

 

테트리스 만들기 전 필수 준비 사항

1. 나는 "꾸준함"과 "인내심"이 있는가?

2. 메인 스레드가 실행될 때(런타임 중 일 때) 변수에 어느 값이 저장됬는지 확인할 수 있는가?
   프로젝트 실행 중 여러 함수들을 호출할 때 특정한 코드에 실행을 멈춰 '조사식' 등을 통해 값을 확인할 수 있는가? 
   즉, "디버깅"을 할 수 있는가? /// 디버깅은 정말 쉽습니다. 진짜 금방 사용할 수 있고 쉽습니다.

3. 구현 중 모르는 내용이나 문제가 있다면 인터넷 혹은 지인등을 참고해서 "내 지식으로 습득"할 수 있는가?

 

 

제가 테트리스를 만들 당시에는 지피티가 없었지만, 요즘엔 지피티가 있기에 더 빠르게 만들 수 있을것 같긴 합니다. 그러나! 과제나 시험이라는 둘레에서 벗어나 내 스스로 설계하고 구현하고 싶다는 마음이 있다면 이 글에서 나오는 모든 요구사항을 지피티한테 물어보고 도출된 로직을 이해하지 않은 채로 복붙하면 안됩니다.

 

이렇게 하다간, 새로운 문제를 직면했을 때 해결하기 어려울 뿐 더러 지피티가 알려준 조각 조각의 로직간 흐름이 안맞을 수 있습니다.

 

결국 내가 프로젝트에 작성한 코드의 로직, 흐름은 내가 알고 있어야 '테트리스'라는 게임을 만들 수 있습니다: ]

 

위에 언급한 3가지 사항에 대한 준비가 되어있다면 테트리스 충분히 만들 수 있습니다. 

 

저는 처음 테트리스 개발 시작을 마음먹었을 때 의지는 넘쳐났습니다. ㅋㅋㅋ,, 근데 보드를 콘솔창에 어떻게 출력하는지 몰라서 정말 막막했었습니다. 시작을 하고 싶어도 지금 콘솔창에서 내가 원하는 위치에 블럭과 테트리스를 표현할 기술이 당장 없었기 때문입니다.

곧바로 위에 언급한 이슈를 해결할 새로운 지식들을 검색하고 습득했습니다.

그렇게 테트리스 보드를 콘솔창에 출력을 성공하게 되었습니다. 그리고 블럭을 콘솔창에 출력하고, 한칸 내려서 출력하고, 출력한 위치를 저장하는 등등의 새로운 문제를 직면 -> 해결 -> 문제 직면...을 반복하다보니 위 동영상처럼 만들 수 있었습니다.

진짜 작지만 커다란? 문제들을 하나씩! 해결해 나가면서 자연스럽게 콘솔창에서 좌표, 2차원 배열을 더 확실하게 제어하는 방법, Sleep함수를 통해 시각적으로 블럭이 내려가는 속도를 제어하는 등의 방법을 알게 되었습니다.

 

테트리스 게임 개발에 필요한 요구사항

 

테트리스를 만들기에 앞서 알고 있으면 좋을 개념들을 소개합니다.

 

  1. 콘솔창에 테트리스 보드 생성 (height, width 명확하게 지정)
  2. 4차원 배열을 다루는 방법 필요. (1차원도 되고 2차, 3차도 되지만 4차원이 7개의 블럭 종류, 특정 블럭의 회전 종류, 특정 블럭의 x, y 표시 방법이 젤 쉽기 때문입니다.)
  3. 7 가지 종류의 블럭을 배열에 저장 후 특정 블럭을 자유롭게 콘솔창에 생성
  4. 방향키를 제어하는 개념 필요
  5. 콘솔창에서 블럭이 방향키에 따라 아래로 한 칸 내려가거나 왼쪽으로 한 칸 이동하는 출력
  6. 스페이스바 또는 특정 키를 선정해서, 이 키보드의 키를 눌렀을 경우 블럭이 콘솔창에서 여러 칸 이상 아래로 내려가도록 출력하기 + 그 내려간 좌표값을 배열에 저장해서 상태를 갖고 있기.
  7. 테트리스 보드의 바텀 영역 높이를 명확하게 제한 후, 블럭이 보드의 바닥 닿았는지 검사 로직 필요
  8. 블럭이 바닥에 닿았을 때, 또는 어떻게 특정 블럭이 특정 시점에서 한 칸 아래로 내려갈 수 있는지 아니면 더이상 내려갈 수 없는 바닥인지를 정의하기
  9. 블럭이 보드의 바닥임을 인지하도록 전역 변수를 통해 어느 시점에 더이상 내려갈 수 없을지에 검사해주는 로직 필요
  10. 블럭이 보드 하단에 쌓인 바닥임을 인지할 수 있는 로직 필요
  11. 테트리스 보드의 특정 가로 줄이 전부 블럭으로 쌓였을 때 이를 탐지하는 로직 필요
  12. 위에서 쌓인 블럭을 제거하는 로직 필요. 그 후 그 위에 쌓여있던 블럭들을 그대로 한칸씩 y값으로 증가하는 로직 필요
  13. 블럭이 생성되는 시점 지정
  14. 게임이 종료되는 게임 오버의 상황 지정
  15. 메인스레드가 함수를 어떻게 실행하는지를 파악해 적절하게 방향키에 따라 블럭이 이동하고, 시간이 지날 때 한칸씩 블럭이 내려가는 로직 구현

 

대략적으로 작성했습니다. 지금보면 엄청 많아보이는데, 사실 콘솔창은 printf()함수 하나만으로 문자열을 화면에 보여줘야하지만 콘솔창을 가로, 세로 네모 도화지로 생각하고.. 내가 원하는 영역을 좌표값으로 확인하기만 한다면 그 좌표값에 printf()로 블럭의 요소 " ㅁ " 를 그려나가면 됩니다. 

 

배열 다루는 방법과 콘솔창에서 좌표값을 확인해 특정 좌표값에 printf()함수로 문자(char)를 출력한다면 절반은 성공한 것입니다. 위에 언급한 개념들이 이 개념만 안다면, 거의 다 절반은 성공한 것입니다. 앞에 언급한 1. ~ 7. 까지의 개념을 습득하고 개발에 적용했을 때, 나머지는 앞의 지식을 기반으로 조금씩만 변형되지 반복되는 작업이고 이때부터 시간만 많다면 구현할 수 있습니다.( 다양한 에러를 체크하고 해결하는데 시간이 들지만, 이미 앞에 다룬 개념들을 바탕으로 구현하기 때문에 크게 어려움은 없을 것입니다.)


다시 말하지만,, 콘솔창에 내가 원하는 지점을 파악하는 좌표!!!! 이 지식만 알면 진짜 80%는 성공입니다. 알고리즘 자료구조 몰라도 됩니다.

 

(cf. 추가적으로 single thread(main thread)가 아니라 multi threads를 활용한다면, 좀 더 유연하고 반응적인 테트리스를 만들 수 있습니다. 근데 multi threads 제어를 하지 않아도 만들 수 있습니다.)

 


 

(23.10.07)아래 내용을 위에서 요약했지만, 아래 내용(2021.09.10 작성)은 예전에 테트리스 개발하면서의 경험과 제가 마주한 문제, 그리고 어떻게 개발이 진행되는지 동영상 + 사진 + 느낀점 등이 병합된 약간의 TMI 형식의 내용으로 구성된 글입니다. 

 

테트리스 개발 때 직면한 문제와 경험을 소개합니다.

테트리스 보드부터, 블럭까지 우선 만드세요. 만드는 도중 또는 만들고 난 후 

그 다음 상황으로 진행하지 못할 때, 그때의 나의 문제점을 파악하고 

서칭을 통해 내 지식으로 습득하면서 해결해나가면 됩니다.

 

구현 전에 생각하면 좋을 전반적인 지식

저의 경험담을 테트리스 구현함에 있어 필요한 전반적인 지식,

알고리즘의 대략적인 개요를 번호, 밑줄로 알려드리겠습니다. 

바쁘신 분들은 분홍색 밑줄 친 부분만 읽어주셔도 됩니다.

 

저는 아무런 자료 없이 테트리스를 만들기 시작할 때,

수많은 난관에 부딪쳤습니다.

2차원 배열에 테트리스 보드를 어떻게 정의하고 콘솔창에 출력할지 고민한 흔적

  1. 보드는 어떻게 만들지?

  2. 블럭은 어떻게 생성하지?

 

이 두 가지를 해결하고 난 다음 또 다른 문제점에 직면했습니다.

 

  3. 블럭과 보드를 각각 2개의 서로 다른 배열로 선언했는데 이 두 개를 어떻게 합치지?

 

 네.. 이때 엄청난 인생의 고민을 했습니다.

결국 성공하고선,

 

  4. 블럭 안에 보드를 집어넣었는데 어떻게 내려가게 하지?

 

  5.블럭이 내려가는데, 블럭이 지워지지 않고 해당 자리부터 아래까지 흔적을 남기면서 내려가네?

이때 너무 감격스러웠는데 음악까지 Extra Climax 상황이 따로 없네요..


아하, 원래 자리에 있던 블럭을 지우면 되는구나?

  6. 한 칸 내려간 블럭을 어떻게 찾아서 출력하지?

  7. 블럭이 땅에 닿아도 쭉 내려가는데 어떻게 처리할까?

 

 

좋았어!!!!!!! 보드 안에 블럭이 블럭 엣지 부분까지 닿고 정지했어!!!


이때가 첫째 날이었던 것 같습니다. 너무 행복해서 잠도 안 왔습니다. 누워서 유튜브로 다른 분들은 테트리스를 어떻게 만들었는지 참고를 하면서 (내가 만들 땐 이렇게 해봐야지~)

 

다음날.

 

보드 안에서 발생하는 방향키 이벤트 처리 

  8.블럭을 새로 또 지정해 놓은 좌표로 추가해서 또 4.5.6.7의 과정을 거치면 돼!!

(너무 간단한걸??)

 이라는 생각도 잠시..

  9. 키보드를 눌렀을 때 블럭을 왼쪽 또는 오른쪽으로 한 칸 이동은 어떻게 하지?

  10. 키보드의 구현_kbhit(),_getch() 함수를 통한 키보드 인식 switch문 구성.

  10.5 아래 방향키 누르면 쭉 내려가도록해야지~

  10.7 스페이스바 누르면 한 번에 내려가도록 해야지~

 이때 쪼금 ,,, 엄청난 시련이 닥칠 거라 예상했지만 답은 너무 간단했습니다.. GotoXY(x-1, y),,

 

  11. 아니 왼쪽으로 계속 이동했더니.. 블럭 옆면 엣지부분을 뚫고 가버리네?

 

이 부분이 좀 시련이었습니다.

어떻게 하면 한 칸 이동했을 때 벽을 인식시킬 수 있을까? 이때 반나절 정도? 많은 고민 끝에 옳지! 미리 GotoXY함수로 한 칸 이동했을 경우에 벽이랑 닿았는지 닿지 않았는지를 파악하는 함수를 만들면 되겠구나! (기존에 블럭을 1로 표시했는데 2로 표시하면 벽과 구분하기 쉽겠구나?(블럭변경))

 

  12. 블럭들을 표현하는 것을 1에서 2로 수정

  13. 블럭이 내려가는데 밑에 쌓여있는 블럭을 인식을 못하네?

 

( 나 : 정말로.. 아니 이런 것까지 해줘야 돼? 게임 만들려면 뭐 하나하나 다 구현해야 돼? 정말 깐깐한 녀석이구만? ㅡ.ㅡ;

 

??? : 아니,,, 당연한 거 아니야? 누가 만들어준대?! 너가 만들어야지. )

 

보드 겉 부분을 통과하지 못하도록 방지하는 로직 필요

  14. 벽면에서 회전했더네 벽을 뚫어버리고선 끝나버리네? 

 

새벽 3시 30분경, "너무 힘들다. 낼 해야지"

 

다음날..

 

"벽을 감지하는 함수는 추가했는데 회전했을 때 벽을 감지를 못하네?" (아놔.. 큰일 났네....ㅠㅠ) 어제 하던 고민을 마저 고민 중,, 1시간 후

 

"아니, 왜 이렇게 간단해?! 그냥 회전했을 때 그 회전한 블럭이 벽에 닿는지 검사하면 되잖아?" (아니 이런 단순한 걸 가지고 뭘 생각하고 있었던 거지?;; - 이렇게 말했지만 사실 어제부터 오늘까지 쭈욱 고민했던 결론 도출)

 

  15. 이번엔 땅에 닿았는데 MaxLine인 상황에서도 없어지질 않네?

 

버그 발견! 해결해야겠다+_+

 

(없앨 때 그냥 없애면 재미없으니까 쫘르륵 맨 왼쪽 끝부터 도미노처럼 없어지는 이펙트도 만들고, 다시 전체가 보였다가 없어졌다 *2 빠르게 한 후 없애야지~ )

 

  16. 멋있게 삭제 이펙트 로직 추가해서 블럭이 쌓인 한 줄을 없애긴 했는데, 위에 쌓여있는 블럭이 아예 안보이네?( 추가적인 버그발견)

  17. 블럭이 왜 안 내려와 ㅡ.,ㅡ 위에 쌓였던 블럭 한 칸씩 내려오는 게 지금까지 코드 구현한 거 중에서 가장 쉬운 건데 왜 안 내려오냐고!!!

 

1시간 뒤..

 


원인 모름..(아직도 같은 생각 같은 코드로 고민 중..)

 

x축이 전부 찼을 때(MaxLine)의 y축을 기준으로

그 위에 부분에 있는 함수들을 1칸 아래에 있는 board에 대입시키면 끝나는데 왜 나만 안돼?,,,,

  18. 이제 남은 것은 새로 블럭이 추가 됐을 때 블럭 바로 아래에 이미 쌓여있는 블럭이랑 닿으면 게임 끝이군?

이 부분도 좀 어려웠습니다. 그러다가 위에 결론이 나온 것입니다.

테트리스 외관도 꾸며야 겠다는 다짐

"동생 동생~~, 내가 겜 만들었는데 함 해보실?"

 

30초 뒤.. 너무 단순한데? 색도 넣고 좀 해봐 효과음이나 음악도 좋 넣고 그냥 딸랑 검은색 바탕에 테트리스랑 블럭들만 덩그러니 있잖아

 

 

(ㅠㅠㅠㅠㅠㅠㅠ)

 

(내가 봤을 땐 완벽한데, 블럭 내려가는 빠르기도 빠르고 척척 촥촥 블럭들도 없어지고 블럭이 이동하는 키 구현까지 완벽한데..)

 

 

이렇게 저의 경험담으로 테트리스의 게임 구현은 대략 3~4일이 걸렸습니다.

 

밤낮으로 임하다 보니 완성할 수 있었던 것 같았습니다. 방학이라 하루종일,, 진짜 하루종일 했습니다.

번외. 테트리스 UI와 지금 내려가는 테트리스 이외 앞으로 나올 블럭 계산하고 표현하는 등의 경험담

이제부터는 번외 편입니다. 

테트리스 외관 구현하기, introFrame, ingameFrame 만들기 등등

 

제 느낌으로는 테트리스 블럭에 색깔을 입히는 것 또한 

어렵고 더 복잡했습니다.

  19. 맞아 테트리스 블럭들은 각각의 개성이 있지? 색을 입혀야겠다. ( 테트리스 글자색 변경 방법 포스트 정리 글 보러가기 )

 

좋아 나는 블럭의 첫 번째 index는 각각의 블럭을 나타낼 수 있으니까 

 

0~6 까지 Int 타입의 변수에서 블럭의 숫자에 따라서 다른 색깔을 입혀주는 함수를 선언하면 되겠네?

(색깔 선언 중..)

 

콘솔 창에 색 출력되는 유니코드를 계속 봐야 하는데 귀찮다.

 

enum이라는 열거형을 배웠었지? 언제 써보겠어,, 지금 써야지~

 

textColor(WHITE);textColor(GREEN);

 

 

오 잘되네,, 

 

  20. 블럭이 땅에 닿았을 때 왜 흰색으로 되는 거지? 

 

아하 내가 블럭이 땅에 닿았을 때 의 상태를 저장하는 Int타입의 2차원 배열에 블럭을 1로 표시를 했구나? 그럼 1 대신 2로 표시해야지.

(오 됐다 블럭이 이젠, 땅에 닿아도 색이 그대로 표시되는구나?)

 

  21. 어라 한 라인이 MAX여서 블럭 한 줄이 사라지고 위에 쌓인 블럭이 내려올 때는 흰색으로 바뀌네?


이 부 분또 한 어려웠습니다.

(그 대안으로 결국 보드의 상태를 나타내는 Int타입의 2차원 배열에서 테트리스 보드 안에 빈 공간을 나타내는 블럭은 0, 벽은 1, 블럭은 2로 저장했는데,, 사실 각각의 블럭은 7개 종류인데 블럭에 2로 모두 동일하게 저장했구나.. 각각의 블럭을 나타낼 고유한 Int를  부여해야겠어!!!

 

이미 2차원 배열에서 0 == 빈 공간, 1 == 보드 벽, 2== 쌓인 블럭 총 3개의 숫자를 사용하고 있는데,

블럭이 쌓인 상태는 전부 2로 저장되어있고,

 

MaxLine이후 과정에서 쌓인 블럭들이 내려올 때도 다 2인 상태이니까 뭐가 무슨 특정한 블럭인지 색 인식을 당연히 못하는구나?

 

좋아. 블럭이 땅에 닿을 때 board에 저장할 때 특정 블럭이 블럭을 나타내는 고유한 index에 +3 을해서 3,

  22.블럭 첫 index 1이면 +3 을해서 4를 board에 저장하는 방식으로 해야겠다!

제 해답은 3씩 전부 더하는 것이었습니다. == 2차원 배열에서 0 == 빈공간, 1 ==보드 벽, 2 == 쌓인 블럭 3 == 기억 블럭, 4 == 니은 블럭 5 == 네모 블럭 etc : )

 

그이후 블럭이 내려올 때는 board에 저장된 숫자-3을 했을 때 각자의 블럭 index를 알 수 있었습니다.

 

(추상적인 글로로 표현해서 무슨 말인지 모르실 수 있습니다..)

 

 

다다 다음 글에 알려드리겠습니다.

 

-몇 마디의 말보다 단 한 줄의 코드가 이해하기 쉽다-

 

  23. 게임창에서 외관 구현하기(방향키, 다음 블럭 구현하기 등)

(이때 저는 대부분의 기업에서 왜 팀으로 일하는지 이유를 대략적으로 알게 되었습니다.)


Design. 정말 어려운 녀석이군.

 

저는 그림도 워낙 못 그려서. 인 게임 배경 형편이 없습니다 ㅠ

꾸미질 못해.....

 

 

계속 해온 거니까 테트리스 보드 옆에 쪼마난 영역의 테두리를 정하고 다음 나올 블럭은 구현하는 거는 껌이지!!

지금까지 내가 GotoXY함수랑 블럭 추가, 삭제한 전적이 얼마나 많은데

 

1시간 후,,

 

아.. 왜 해당 블럭이 안 없어지지?( 그 쪼매나고 쉽다고 말한 다음 나올 블럭 구현 중...)

(2시간 후)

드디어 됐다.

(자만하면 안 되겠네..)

 

  24.intro창을 만들고 게임 난이도 입력 후 system("cls")를 한 후 입력한 값을 참조해서 게임에 반영하기.

  25. 콘솔 창에서 음악 재생은 어떻게 하는 것인가?

  26.wav파일을 이용한 PlaySound() 함수는 한 개의 wav파일만 재생이 가능한데 다른 방법은 없을까?

우선 PlaySound() 함수를 이용해서 한번|끝까지|재생

일하자!

  27.mp3파일을 사용하는 방법이 있구나, 어떻게 구현하지?(진행 중입니다. -> 진행 완료)

테트리스 구현의

대략적인 알고리즘은 이렇습니다.

 


테트리스 소스코드

 

 

아래 글은 제가 테트리스를 구현한 소스코드입니다.

https://github.com/SHcommit/Tetris-game-in-C-lang

 

GitHub - SHcommit/Tetris-game-in-C-lang: Creating a Tetris game in c language.

Creating a Tetris game in c language. Contribute to SHcommit/Tetris-game-in-C-lang development by creating an account on GitHub.

github.com

긴 글 읽어주셔서 정말 감사합니다.

 


테트리스 게임 만들기 시작! Ep2

 

 

 

다음 글은

이제 테트리스를 만들기 전에 필수적으로 알아야 할

함수들에 대해서 소개해드리겠습니다.

 

(하고 싶은 말은 끙끙 앓기보다,

우선 시작하시고

모르는 것이 있으면 조금 멈추어 자신이 필요한 부분을 배우고

또다시 도전하면 성공!이라는 말입니다.)

같은 말 반복해서 죄송합니다.

저도 처음엔 아무것도 몰랐었는데 하다 보니 성공해서 말씀드리는 겁니다.,,

2021.09.11 - [C언어/프로젝트] - [C언어] 초보자도 구현할 수 있는 테트리스 게임 만드는법 ep.2 커서다루기, 블럭 응용 표현하기