티스토리 뷰

학부 2학년 1학기 때 만들었던 테트리스..

몇 년이 지나 이제야 올려봅니다.

 


가장 처음, 어떤 기능을 가진 Class들을 정의할 것인지 봐야 한다.

GUI는 적용하지 않고 Console환경에서 실행할 것이다.

Class는 간단한 것에서부터 복잡한 거 순서대로 설명하겠습니다.

 

 

 

 

 

1. Point Class

흔히 PS에서도 접해볼 만한 Point Class.

배열의 y, x를 다루는데 class형태로 만들어서 조금 부가적인 기능을 더 해준 형태라고 보면 된다.

 

 

#ifndef KIBBOMI_POINT
#define KIBBOMI_POINT

///배열의 좌표, Console화면의 좌표를 설정
class Point
{
public:
	Point(int x = -1, int y = -1);
	int GetX() const { return x_; }
	int GetY() const { return y_; }
	void SetX(int x) { x_ = x; }
	void SetY(int y) { y_ = y; }
	Point operator+(const Point &pt);

	static void GotoXY(int x, int y);
	static void GotoXY(Point pos);
	static Point GetScrPosFromCurPos(const Point &pos);

private:
	int x_;
	int y_;
};

#endif

 

멤버 변수인 x, y가 존재한다. 간단한 Getter, Setter가 있고, Point만의 +연산자 오버로딩을 해주자. 이런 경우에는 정말 유용하다.

그리고 Console화면에서 커서의 위치를 움직여 줘야 하는 경우가 있기 때문에 GotoXY함수를 2개 오버로딩 했다.

현재 x_, y_좌표에 대응하는 Consol화면의 커서 위치를 얻고 싶을 때 사용하는 GetScrPosFromCurPos라는 함수를 정의했다.

 

요약하면

→ Console화면 & Board의 좌표만 관리

 

 

2. Board Class

우선 테트리스 하면 가장 먼저 생각나는 테트로미노, 테트로미노는 일정 크기를 가진 배열에서 표시될 것이다.

 

#ifndef KIBBOMI_BOARD
#define KIBBOMI_BOARD

#include "Point.h"

#define EMPTY -1

///맵을 나타내는 Class
class Board
{
public:
	Board();
	int GetState(Point pos);                // Pos 위치의 상태를 반환함 : 단순히 board_[][] 값을 반환하면 됨
	void SetState(Point pos, int state);    // Pos 위치의 값을 State 값으로 설정함
	int CheckLineFull(Point reference_pos); // 특정 행이 테트로미노로 가득 차 있는지 조사, 가득 차 있다면 그 위의 라인들을 한 칸씩 내림
											// 최종적으로 삭제된 라인 수 반환
	void OpenBackgroundSound(void);//사운드 출력 
	void CloseBackgroundSound(void);//사운드 끄기

private:
	int board_[10][20];
};

#endif

Board class는 Console에서 1명의 player의 맵을 관리해주는 역할을 한다.

예를 들면, 어떤 한 줄이 가득 찼는지 조사하는 것이 있을 것이다. 그 외 Getter Setter이 있다.

사운드 출력과 끄는 것은 딱히 넣을 곳이 없어서 Board class에 넣었다.

 

요약하면

→ 맵만 관리

 

 

3. Score Class

그다음, 점수를 관리할 Class가 필요할 것이다.

Ranking도 관리해야 할 거고 벽돌이 없어질 때마다 점수를 갱신해주어야 한다.

뭐 굳이 이렇게 Score로 안 빼고 Board에 편입시켜도 될 것 같지만 객체지향을 살리기 위해..

 

확실히 이렇게 분리해서 모듈화를 하는 게 나중에 유지, 보수할 때 충돌도 없고 깔끔하다.

 

#ifndef KIBBOMI_SCORE
#define KIBBOMI_SCORE

#include "Point.h"

///점수를 관리
class Score
{
public:
	Score();                                                // score_ 값을 0으로 초기화
	void UpdateScore(Point reference_pos, int increment);   // Increment 만큼 점수를 더해 줌
	void Print(Point reference_pos);                        // 점수 출력 위치에 점수 출력
	int GetScore(void) { return score_; }
	static void ReadRanking(void);
	void SaveRanking(Point reference_pos, int score_);
private:
	int score_;
	Point score_pos_;
};

#endif

 

UpdateScore 같은 경우는 점수를 increment만큼 증가시키고 Point 자리에 다시 출력하는 역할, 이때 출력은 Print로,

점수를 얻는 getter 등 을 정의하고 있다.

Ranking 관련해서는 파일 입출력을 사용한다.

 

요약하면

→ 점수만 관리

 

 

4. Controller Class

이제 조금 테트리스 게임 같은 부분을 맡는 Class를 정의해볼 것이다.

 

게임의 구동부는 다른 Class가 하고 이 Class는 약간 User Interface 같은.. 게임 - 유저 간의 대화방법을 약속하는 부분을 맡는다.

그냥 간단히 말하면 키 설정을 하고 키 입력을 받는 Class.

 

#ifndef KIBBOMI_CONTROLLER
#define KIBBOMI_CONTROLLER

#define KEY_ESC 27
#define KEY_ENTER 13
#define KEY_UP (256 + 72)
#define KEY_DOWN (256 + 80)
#define KEY_LEFT (256 + 75)
#define KEY_RIGHT (256 + 77)
#define KEY_SPACE 32
#define KEY_SAVE 99

class Controller
{
public:
	void SetController(int key_esc, int key_right, int key_left, int key_rotate, int key_down, int key_space, int key_savetetromino);
	int Getkey_esc() { return key_esc_; }
	int Getkey_right() { return key_right_; }
	int Getkey_left() { return key_left_; }
	int Getkey_rotate() { return key_rotate_; }
	int Getkey_down() { return key_down_; }
	int Getkey_space() { return key_space_; }
	int Getkey_savetetromino() { return key_savetetromino_; }

	static int GetKey();    // 현재 입력된 키 알아오기

private:
	int key_esc_;           // 테트리스 종료
	int key_right_;         // 오른쪽으로 이동
	int key_left_;          // 왼쪽으로 이동
	int key_rotate_;        // 회전
	int key_down_;          // 한 칸 내려가기
	int key_space_;			// 한번에 내려오기
	int key_savetetromino_;	//저장
};

#endif

 

우선 생성자로 처리해도 되지만 따로 SetController라는 함수를 정의했다.

사용자 정의에 의해(사전 정의), 매개변수로 들어오는 Key값을 설정한다.

즉, 아직 Tetris Class를 설명하지는 않았지만, 1 player 당 1개의 Controller Class를 갖고 있다고 보면 된다.

GetKey함수를 통해 받은 Key가 예를 들어서 왼쪽 화살표다 그러면 다른 Class에 정의된 Handler에게 넘겨주어서 그에 따른 처리를 하면 된다.

 

특별한 것은 없다. 이 함수도 Getter, Setter가 있고, 현재 입력된 키를 알아오는 정적 함수 GetKey()가 있다.

 

 

요약하면

→ 키 설정 및 키 입력만 관리

→ 키 입력만 하고 그 뒤는 다른 Handler에게 전달함.

 

 

 

5. Tetromino Class

Tetromino Class를 정의해보자. 테트리스 게임에서 Class를 만들 때 가장 먼저 떠오르는 부분이 아닐까 싶다.

 

#ifndef KIBBOMI_TETROMINO
#define KIBBOMI_TETROMINO

#include "Point.h"
#include "Board.h"
#include "Option.h"	//for static members

#define COL 		GetStdHandle(STD_OUTPUT_HANDLE)
#define BLACK 		SetConsoleTextAttribute(COL,0x00f0);
#define DARK_BLUE	SetConsoleTextAttribute(COL,0x00f1);
#define GREEN 		SetConsoleTextAttribute(COL,0x00f2);
#define BLUE_GREEN 	SetConsoleTextAttribute(COL,0x00f3);
#define BLOOD 		SetConsoleTextAttribute(COL,0x00f4);
#define PURPLE 		SetConsoleTextAttribute(COL,0x00f5);
#define GOLD 		SetConsoleTextAttribute(COL,0x00f6);
#define ORIGNAL		SetConsoleTextAttribute(COL,0x00f7);
#define GRAY 		SetConsoleTextAttribute(COL,0x00f8);
#define BLUE		SetConsoleTextAttribute(COL,0x00f9);
#define HIGH_GREEN	SetConsoleTextAttribute(COL,0x00fa);
#define SHY_BLUE 	SetConsoleTextAttribute(COL,0x00fb);
#define RED 		SetConsoleTextAttribute(COL,0x00fc);
#define PLUM 		SetConsoleTextAttribute(COL,0x00fd);
#define YELLOW 		SetConsoleTextAttribute(COL,0x00fe);
#define WHITE		SetConsoleTextAttribute(COL,0x00ff);//모두 흰배경+색깔 로 설정함

#define TETROMINO_TYPE_COUNT 7
#define ROTATE_COUNT 4
//const Point g_next_tetromino_init_pos(29, 3);
const Point g_next_tetromino_init_pos(13, 17);
const Point g_cur_tetromino_init_pos(4, 20);

enum TETROMINO_TYPE { TETROMINO_I, TETROMINO_J, TETROMINO_L, TETROMINO_O, TETROMINO_S, TETROMINO_T, TETROMINO_Z };

class Tetromino
{
public:
	Tetromino(Board *board, TETROMINO_TYPE type);
	virtual void Draw(Point reference_pos);			        // 현재 화면 중심 위치를 기준으로 그리기
	virtual void Erase(Point reference_pos);		        // 현재 화면 중심 위치를 기준으로 지우기
	bool MoveDown(Point reference_pos);		        // 한 칸 내려오기, 유효한 위치면 다시 그림
	void MoveRight(Point reference_pos);	        // 한 칸 오른쪽으로 이동, 유효한 위치면 다시 그림
	void MoveLeft(Point reference_pos);		        // 한 칸 왼쪽으로 이동, 유효한 위치면 다시 그림
	void Rotate(Point reference_pos);		        // 회전, 유효한 위치면 다시 그림
	void GoBottom(Point reference_pos);		        // 최하단으로 이동, 유효할 때까지 MoveDown() 실행
	virtual void MarkCurTetrominoPos(Point reference_pos);	// 다 내려오고 난 후에 board_의 객체에 대한 값 setting
	bool CheckEndCondition(void);					// 종료 조건 검사, y축 기준으로 20이상(가득 참)이면 종료(return true)
	void SetCenterPos(Point pos);					// 중심 위치 설정    
	Point GetCenterPos(void) { return center_pos_; }

	Point Shadowtetromino(Point reference_pos);
	bool ShadowtetrominoCheck(Point temp);
	void ShadowDraw(Point reference_pos, Point temp);
	void ShadowErase(Point reference_pos, Point temp);

	void SaveDraw(Point reference_pos);
	void SaveErase(Point reference_pos);
	virtual bool CheckValidPos(void);	        // 현재 테트로미노 위치가 유효한지 검사, 판을 벗어나지 않고 다른 테트로미노가 쌓여있지 않음

protected:
	Point center_pos_;    // 현재 중심 위치 (내부 배열 기준)
	TETROMINO_TYPE type_;               // 테트로미노 타입
	int rotate_;				        // 현재 회전 위치
	Board *board_;			 // Board 객체 정보를 알기 위해 Board 객체와 연결    
};

#endif

조금 많이 복잡하다.

한줄한줄 이해해보자

#define 부분은 색깔을 지정해주기 위해 정의한 부분이다.

 

#define TETROMINO_TYPE_COUNT 7
#define ROTATE_COUNT 4

이 부분은 테트로미노 타입, 그리고 방향 4가지를 가독성 좋게 표기하기 위해 사전 정의했다.

Type도 enum형태로 가독성을 높이기 위해 정의했다.

 

Tetromino는 다음에 상속을 고려해서 virtual 등등 protected 등으로 설계했다. 결과적으로 상속은 하지 않았지만..

 

Tetromino는 1개의 테트로미노를 정의하고 관리한다는 것을 유념하고 분석해보자.

 

생성자를 보면

Board의 객체 정보를 알기 위해 Board객체와 연결했다. 그리고 현재 테트로미노의 타입을 정의한다.

ㄱ자 모양인지 ㅣ,ㅗ모양인지 ㅁ모양인지 등등.

 

그리고 매개변수로 넘기는 Point를 기준으로 현재 Tetromino를 그리거나 지우는 멤버 함수를 정의했다.

테트로미노를 한 칸 내리거나, 오른쪽, 왼쪽, 회전하거나 혹은 한 번에 바닥까지 내리는 멤버 함수도 정의했다.

 

그리고 이동했을 때 Board의 값이 변경되어야 할 것이다. 그 값을 변경해주는 MarkCurTetrominoPos를 정의했다.

게임이 끝날 조건 즉, 테트로미노가 생기는 위치에 다른 블록이 있다면 게임이 끝나는 조건이다.

그 부분을 감지하는 CheckEndCondition을 정의했다. 

그리고 CenterPos의 getter setter가 있다. 이 CenterPos가 무슨 역할을 하는지는 멤버 변수 설명할 때 말씀드리겠습니다.

 

또, 이 기능은 추가적인 부분이지만 밑에 그림자(Shadow)를 그려주는 함수이다.

Shadowtetromino ~ ShadowErase까지.

개념만 설명하면 이전에 정의했던 내릴 수 있는 가장 맨 밑으로 보내는 함수가 있었다. 그 함수를 실행시켜서 실제 Tetromino는 움직이지 않지만 위치 정보를 얻어내서 Tetromino의 위치가 갱신될 때마다 그 위치에 Shadow를 그려주는 것이다.

 

이 기능 또한 추가적인 부분인데, 블록을 하나 저장하는 기능이다.

SaveDraw , SaveErase 이 부분.

컨트롤러로부터 블록 1개를 저장하는 키가 입력이 되면 이 함수가 실행이 되는데, 

사전에 정의한 Save블록이 저장될 위치에 Tetromino를 1개 저장한다.

Save테트로미노에 대한 정보는 포인터 형태로 다른 클래스에서 가지고 있게 된다.

이 Tetromino클래스에서는 '그려주는 것' 까지만 한다.

 

마지막으로 중요한 함수

Tetromino를 움직이거나 회전할 때, 다음 위치가 갈 수 있는 위치인가? 를 나타내 주는 함수이다.

예를 들어 왼쪽 벽에 닿아있는데 계속 왼쪽으로 움직이는 키를 입력하면 아무것도 동작하지 않아야 한다. 따라서 이 함수가 필요하다.

 

이제 멤버 변수를 보자.

Point center_pos_가 있는데 이것은 각 블록의 중심위치를 나타낸다. 회전할 때 이 중심위치를 가지고 회전하게 된다.

그리고 테트로미노의 타입을 나타내는 변수와 회전할 때 몇 번째 블록인지, ㅣ로 예를 들면 ㅡ 2가지가 있을 수가 있다.

이 상태 중에서 어떤 상태를 나타내는지 저장하는 값이다. 마지막으로 Board객체의 정보를 얻기 위해 board포인터를 갖는다.

 

요약하면

테트로미노 1개만 관리

 

6. Tetris Class

테트리스 게임 전체를 관리하는 Class이다.

Tetris Class에는 멤버 변수로 현재, 다음, 저장한 테트로미노의 포인터를 갖고 있고, Board, Score, Controller들을 모두 한 곳에 모아서 실행시키는 역할을 한다. 

#ifndef KIBBOMI_TETRIS
#define KIBBOMI_TETRIS

#include "Point.h"
#include "Tetromino.h"
#include "Controller.h"
#include "Board.h"
#include "Score.h"
#include <ctime>
using namespace std;

class Tetris
{
public:
	// 생성자 : 기준 위치, 컨트롤 키 설정 가능
	Tetris(Point reference_pos = Point(1, 1), int key_esc = KEY_ESC, int key_right = KEY_RIGHT, int key_left = KEY_LEFT, int key_rotate = KEY_UP, int key_down = KEY_DOWN, int key_space = KEY_SPACE, int key_savetetromino = KEY_SAVE, int player_ = 1);
	void Run(void);                             // 테트리스 실행 : running_이 true인 동안 RunStep을 반복적으로 실행
	void RunStep(void);                         // 키보드 키가 눌러진 경우(kbhit()으로 조사) KeyHanler 실행, 아닌 경우 NormalStep 실행
	bool KeyHandler(int key);                   // 눌러진 키에 따라 실행
	void NormalStep(void);                      // 한 칸 내려오기 실행
	bool IsRunning(void) { return running_; }   // 실행중 여부 : running_ 값 반환
	void Changefallingspeed(void);
	void SaveTetromino(void);

protected:
	void DrawBoard();               // 테트리스 게임 틀 그리기
	void PrepareNextStep(void);     // 현재 테트로미노가 완전히 내려온 후 다음 테트로미노 준비
	void GenerateNextTetromino();   // 다음 테트로미노 생성
	void GetCurTetrominoFromNext(); // next_tetromino_로부터 cur_tetromino_로 이동 (중심위치, 화면위치 갱신)
	double GetDiffTime(void);       // 현재시간 - 기준시간(start_time_) 반환 (미리 제시함)
	void SetCursorInvisible(void);  // 커서 안보이게 하기

private:
	Point reference_pos_;           // 화면상에 나타낼 기준 위치 (1, 1)인 경우 실제 (0, 0)으로 맵핑됨
	bool running_;                  // 종료 조건인 경우 이 값이 true가 됨
	Board board_;                   // 테트로미노가 떨어지는 2차원 배열 관리
	Score score_;                   // 점수 출력 관리
	Tetromino *cur_tetromino_;      // 현재 떨어지는 테트로미노 가리킴
	Tetromino *next_tetromino_;     // 다음 테트로미노 가리킴
	Tetromino *save_tetromino_;		// 저장 테트로미노 가리킴
	Controller controller_;         // 이동 키, 회번 키, 빨리 떨어지기 키 등 조정 버튼(키) 관리
	clock_t start_time_;            // 기준 시간 설정 : 현재 시간과의 차이를 통해 한 칸씩 내려오기 여부 결정(GetDiffTime()에서 사용)
	double falling_speed_;          // 떨어지는 속도(디폴트 0.8) : 일반 모드에서 GetDiffTime()의 값이 falling_speed_ 값보다 크면 한 칸 내려옴
	int player_;					// 1p 인지 2p인지 구분
};

#endif

 

위의 생성자는 1p를 기준으로 디폴트 매개변수 값을 지정해놓았다. 각 멤버 변수, 멤버 함수들의 역할은 주석으로 설명을 달아 놓았다.

 

요약하면

→ Player 1명의 Tetris게임을 관리

 

7. Option Class

옵션을 관리한다.

부가적인 기능인 Shadow나 떨어지는 속도 조정, 블록 저장 기능 등등을 ON / OFF 할 수 있는 화면 및 세팅을 관리한다.

 

이 부분은 몇 년 뒤에 수정한 부분이라 그냥 대충 동작하도록 했기 때문에.... 맘엔 안 드는 부분

#ifndef KIBBOMI_OPTION
#define KIBBOMI_OPTION

#include "Controller.h"
#include "Point.h"
#include <string>
using namespace std;

class Option {
	public:
		Option();
		int show_main();	//선택 값 반환
		void show_option();
		static bool opt_ghost, opt_save;
		static double opt_fallingspeed;
	private:
		Controller controller_;
		Point point_[5];
		string opt_str[4];
};

#endif

 

이 부분의 falling speed는 Tetris class의 falling speed를 static으로 선언하면 굳이 선언하지 않아도 되는 부분이다.

option class는 Main에서 Option화면으로 넘어갔을 때의 키 처리, 값 설정 등을 담당한다.

 

요약하면

→옵션 화면 및 옵션 설정 값만 관리

 

 

 


개발한 테트리스 게임은 특별히 상속하거나 그런 복잡한 관계는 없다.

그냥 모두 각각의 역할을 맡아서 작동한다.

 

이로써 테트리스 게임에 필요한 간단한 클래스들을 정리해보았습니다.

다음 글은 실제 구현이나 flow chart에 대해서 알아보겠습니다.

 

 

 

지적 댓글 환영입니다 :)

댓글
«   2024/05   »
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
Total
Today
Yesterday