Notice
Recent Posts
Recent Comments
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
Archives
Today
Total
관리 메뉴

Grobble

간단히 테트리스 구현한 후기 본문

기타

간단히 테트리스 구현한 후기

smilu97 2021. 8. 7. 01:17

 

https://github.com/smilu97/hamtris

 

GitHub - smilu97/hamtris: C++ Tetris implementation on GUI, CUI

C++ Tetris implementation on GUI, CUI. Contribute to smilu97/hamtris development by creating an account on GitHub.

github.com

최근 주변에 테트리스를 연습삼아 만드는 사람이 있어서 나도 한번 만들어보기로 했다.

테트리스는 원래 프로그래밍을 이제 막 시작하는 사람들이 입문단계에서 많이 도전하는 주제라고 생각하는데, 실제로 나도 어렸을 때 만들어보기도 했고, 도움이 많이 되었던 것 같다.

Super rotation rule

테트리스에는 T-spin이나 Z-spin, S-spin 같은 어려운 기술 같은 것들이 존재하고 이 기술들이 존재할 수 있는 이유는 테트리스의 rotation에 wall kick이라는 개념이 있어서라고 하는데... 생각보다 잘 정리되고 깔끔한 규칙이 있었다.

https://tetris.fandom.com/wiki/SRS

 

SRS

Super Rotation System, or SRS is the current Tetris Guideline standard for how tetrominoes behave, in a broad sense. SRS represents where and how tetrominoes spawn, how they rotate, and what wall kicks they may perform. In TI, a player may choose between W

tetris.fandom.com

위키를 보면 각 테트리미노의 초기 생성시 회전방향과 회전 시 모양의 변화 등이 모두 정의되어있고, 제자리에서 회전할 수 없을 경우 어떻게 해야할 지가 나와있다.

 

Wallkick이 바로 이 제자리에서 회전할 수 없을 경우 행해지는 것을 지칭하는 단어인데, I-mino같은 길쭉한 테트리미노들이 벽에 붙었다가 회전할 경우 원래는 벽에서 떨어진 후에 회전해야 되는데 이 일련의 과정을 생략해서 자동으로 행해주는 것을 보고 벽을 차는 것 같아서 Wallkick이라고 명명한 게 아닐까 생각한다.

Wallkick은 그런데 단순히 벽을 차는 느낌의 것뿐만 아니라 원래 같으면 집어넣을 수 없을 것 같이 보이는 위치에 테트리미노를 껴넣는 것을 가능케 한다...

 

테트리미노를 회전할 때, 제자리에서 회전 하는 것이 불가능 할 경우 시스템은 테트리미노를 이리저리 움직인 후에도 회전할 수 없는지를 검사하는 데, O-mino를 제외한 나머지 테트리미노들 모두 5가지의 오프셋을 순서대로 시험해보게 되어있다.

이 5가지 오프셋은 I-mino와 JLSTZ-mino 두 가지 경우의 것들이 있는데 꽤 먼 오프셋들이 존재한다.

 

그러다 보니 T-spin으로 3줄을 지우거나, 이상한 곳에 S-spin으로 2줄을 지우는 등의 것들이 가능해진다... ㅋㅋ

https://www.youtube.com/watch?v=_Pu4wHzncnw 

CLI 에서 테트리스 그리기

예전에는 별로 할 줄 아는 것도 없었고 자신도 없어서 예쁘게 그리는 것에 별로 관심이 없었는데, 이제는 CLI에서 트리키한 방법으로 예쁜 그림을 그리는 것들을 많이 봐와서 테트리스 정도는 적당히 예쁘게 출력할 수 있지 않을까 하는 생각에 연구해보았다.

원래는 이것저것 시험해보다가 @ 문자가 그나마 제일 꽉차고 예쁜 느낌이 들어서 @와 . 만으로 모든 출력을 커버하다가...

CLI에서도 색깔이 들어간 정사각형을 그릴 수 있다는 걸 발견했는데, 이 박스들은 사실 배경색이 칠해진 공백문자 2개로 이루어져있다.

https://en.wikipedia.org/wiki/ANSI_escape_code

 

ANSI escape code - Wikipedia

Method using in-band signaling to control the formatting, color, and other output options on video text terminals ANSI escape sequences are a standard for in-band signaling to control cursor location, color, font styling, and other options on video text te

en.wikipedia.org

ascii escape code를 이용하면 stdout에 출력되는 문자에 스타일을 지정하는 것이 가능한 걸로 보이는데, 배경색도 지정하는 것이 가능하다.

ESC[ 48;5;⟨n⟩ m  <== 이런식으로 stdout에 출력하면 이 뒤로 모든 문자에 배경색이 붙게 되는데 (n은 색깔 코드)

예를 들어

void switchBackground(Color color) {
  if (color == COLOR_RESET) {
    printf("\e[0m");
    return;
  }
  
  printf("\e[48;5;%dm", (int) color);
}

이런 함수를 만들어서 사용하는 식으로 쓸 수 있었다.

OpenGL 도전

사실 OpenGL을 써본적이 없어서 (그래픽스 수업을 들었어야 됬는데..) 좀 무섭기는 했는데 막상 써보니까 WinAPI랑 별로 다른 점이 없었다.

glut 라이브러리를 사용하니까 굉장히 편하게 원하는 그래픽을 출력할 수 있었다.

 

Callback을 인자로 받는 함수에 메소드를 어떻게 넘기나

glut에서는 여러 이벤트에서 어떻게 대처해야 할지 알려줄 수 있는 callback 등록함수를 다수 가지고 있는데, (glutDisplayFunc, glutTimerFunc, glutKeyboardFunc, glutSpecialFunc 등) 인자로 받는 함수 포인터 자리에 클래스 메소드를 넣고 싶은데 적절한 방법을 찾지 못했다. 결국

typedef void (*DisplayFunc)();

class TetrisGame {
  void Init(..., DisplayFunc displayFunc) {
    ...
    glutDisplayFunc(displayFunc);
  }
};

TetrisGame g_game;

void displayFunc(void) {
  g_game.Render();
}

int main() {
  g_game.Init(..., displayFunc);
  
  return 0;
}

이런 식의 이상한 구조로 작성할 수 밖에 없었는데... 메소드라는 것은 결국 첫번째 인자로 인스턴스 주소를 받는 것이기 때문에 애초에 불가능한 게 아닐까 생각이 들고, std::bind를 써서 혹시나 깔끔하게 처리할 수 있지 않을까 싶었지만 역시 불가능했고 람다를 사용하는 것도 역시 실패했다.

 

glEnable

OpenGL은 기본적으로 생각보다 많은 기능들이 Disable상태로 시작되는 것 같다...

Sprite를 읽어서 Texture로 변환한 뒤에 출력하는 간단한 것 조차 GL_TEXTURE_2D라는 기능이 꺼져 있으면 안된다는 것을 몰라서 몇 시간을 삽질했다.

 

여기저기 구글링 해봐도 Texture 생성 자체는 많은 예제가 있었지만 glEnable(GL_TEXTURE_2D); 코드를 삽입하라는 얘기는 거의 없었다.

 

또 Texture에 Alpha채널을 적용시키는 것도 당연히 기본적으로 작동할 줄 알았으나

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
glClearColor(0.0, 0.0, 0.0, 0.0);

기능을 몇 가지 활성화시켜야만 했다.