본문 바로가기

programming/내가 만들고 싶어서 만든 것!

C++를 활용한 오목게임 만들기 - PvP모드의 OOP적 설계

반응형

1. 개요

 

 방학 때, 들은 특강을 계기로 만들어 보았던 오목 프로그램. 학기가 지나고 같은 특강을 똑같이 신청하게 되었다. 이유는  Class에 대한 문법은 알아도 이를 활용한 OOP적인 설계는 하지 못했기 때문이다. 내가 블로그에 올린 오목게임의 베타버전을 보면 알겠지만, 하나의 클래스가 거의 main()함수의 역할을 하는 절차지향적인 설계를 볼 수 있을 것이다. 베타버전의 오목게임을 보고싶으면 아래 링크를 통해 확인할 수 있다.

 

오목게임 베타버전: apape1225.tistory.com/32?category=815520

 

C++를 활용한 오목게임 만들기(베타버전) - array 이차배열 구현(STL)

1. 개요  방학 때, 교수님께서 따로 진행하시는 특강을 듣게 되었는데 최종 과제로 제출한 프로그램을 올려볼까 한다. C++ 14버젼 이상의 문법을 설명해주시는 특강이었는데 STL 자료구조에 대해

apape1225.tistory.com

 그래서 OOP적인 코드작성이 무엇인가? 라는 질문에 답을 생각해봤을 때 필자가 학교 강의에서 배운 이론으로는 "유지 보수가 좋다."와 같은 것들이 있을 수 있겠다. 그러나 내가 배운것이라고는 Class와 관련된 문법(가상함수, 함수 재정의) 그리고 약간의 STL이다. 아무리 연습해도 문법에 익숙해질 뿐, 직접적인 설계에 관한 감은 잡지 못하는 것이 사실이다. 이번 포스팅에서는 전에 작성한 베타버전을 "Class와 상속"을 통해 OOP적인 코드를 작성하였다.

 

2. 기능분류

 

 내가 그렇게 무거운 프로그램을 작성한 것은 아니기 때문에 생각한 것일 수도 있지만 아마 OOP적인 설계는 "공통기능 찾기" 라는 생각이 들었다. 이 게임에서 내가 작성하고 싶은 기능은 두가지 이다. 하나는 두명이서 할 수 있는 PvP모드의 오목이고 하나는 한명이서 플레이 할 수 있는 AI모드이다. 이렇게 생각하니 두 모드의 공통기능을 구현한 클래스 하나를 구현하고 각각의 클래스를 상속받도록 하면 깔끔하게 정리된 코드가 될 것 같았다. 이번 포스팅에서는 베타버전을 이용해 공통기능을 구현한 Concave클래스와 PvP모드 게임을 구현한 ConcavePvP클래스를 만들었다. 다음은 내가 스스로 정리해본 클래스의 기능이다.



 

3. 코드작성

 

- InfoWindow Class

 

 InfoWindow Class는 인터페이스를 담당하는 Class라고 생각하면 좋다. 아래의 실행결과를 보면 안내판역할을 하는 화면과 실제 게임이 진행되는 보드판으로 게임이 구성된다. 



 베타버전에서는 오목판을 출력하는 함수는 다른 클래스에 지정하였지만 이번에는 인터페이스를 구성하는 모든 화면을 전부 InfoWindow Class에 구현하였다. 다음은 소스코드이다.

 

- InfoWindow.h

 

#pragma once
#include <Windows.h>
#include <cstdio>

#define SIZE 15

using namespace std;
class InfoWindow
{
private:
	int startLocationX;
	int startLocationY;

public:
	InfoWindow();
	~InfoWindow();

protected:
	void printBord();
	void printInfo();
	void printfMessage(const char *msg, int originX, int originY);
	void gotoXY(int x, int y);
};

 

- InfoWindow.cpp

 

#include "InfoWindow.h"

InfoWindow::InfoWindow()
{
	startLocationX = 35;
	startLocationY = 0;
}

InfoWindow::~InfoWindow()
{
}

void InfoWindow::printInfo() {
	gotoXY(startLocationX, startLocationY);
	for (int i = 0; i < 40; i++) {
		printf("-");
	}
	for (int i = 1; i < 14; i++) {
		gotoXY(startLocationX, startLocationY + i);
		printf("|");
		for (int i = 0; i < 38; i++) {
			printf(" ");
		}
		printf("|");
	}
	gotoXY(startLocationX, startLocationY + 14);
	for (int i = 0; i < 40; i++) {
		printf("-");
	}

	gotoXY(startLocationX + 3, startLocationY + 8);
	printf("w : 위로 한칸 이동.");
	gotoXY(startLocationX + 3, startLocationY + 9);
	printf("a : 왼쪽으로 한칸 이동.");
	gotoXY(startLocationX + 3, startLocationY + 10);
	printf("s : 아래로 한칸 이동.");
	gotoXY(startLocationX + 3, startLocationY + 11);
	printf("d : 오른쪽으로 한칸 이동.");
	gotoXY(startLocationX + 3, startLocationY + 12);
	printf("h : 착수");

}

void InfoWindow::printfMessage(const char *msg, int originX, int originY) {
	gotoXY(startLocationX + 2, startLocationY + 4);
	for (int i = 0; i < 37; i++)
		printf(" ");
	gotoXY(startLocationX + 1, startLocationY + 4);
	printf("%s", msg);
	gotoXY(originX, originY);
}

void InfoWindow::gotoXY(int x, int y) {
	COORD Pos;
	Pos.X = x;
	Pos.Y = y;
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), Pos);
}

void InfoWindow::printBord() {
	gotoXY(0, 0);
	int i, j;
	printf("┌");
	for (i = 0; i < SIZE - 2; i++)
		printf("-┬");
	printf("-┐\n");

	for (i = 0; i < SIZE - 2; i++) {
		printf("├");
		for (j = 0; j < SIZE - 2; j++)
			printf("-┼");
		printf("-┤\n");
	}
	printf("└");
	for (i = 0; i < SIZE - 2; i++)printf("-┴");
	printf("-┘");
}

 

 콘솔창에서 좌표를 정해 출력하는 방식을 사용하였기에 좌표의 위치를 지정하는데 많은 집중을 기울였다. startLocationX, startLocationY 변수는 안내판 위치의 가장 왼쪽 상단의 좌표이다.

 

- Concave Class

 

 Concave Class는 PvP모드와 AI모드의 공통된 기능이 구현된 코드이다. 승패를 판단하는 코드가 주된 코드인데, 이에 대한알고리즘은 베타버전에 설명되어있다. 소스코드이다.

 

- Concave.h

 

#include <array>
#include "InfoWindow.h"
#define SIZE 15
#define LOCATION 15

using namespace std;

#pragma once

class Concave
{
private:
	array<array<int, SIZE>, SIZE> bord;
	int posX;
	int posY;
	bool colorOfStone;

public:
	Concave();
	~Concave();

protected:
	bool checkWin();
	bool checkRange(int x, int y);
	bool changeBord(int x, int y, bool value);
	bool moveX(int direction);
	bool moveY(int direction);
	int getX() { return posX; }
	int getY() { return posY; }
	void reverseColor() { colorOfStone = !colorOfStone; }
	bool getColor() { return colorOfStone; }
};

 

- Concave.cpp

 

#include "Concave.h"



Concave::Concave()
{
	for (int i = 0; i < SIZE; i++) {
		for (int j = 0; j < SIZE; j++)
			bord[i][j] = 0;
	}
	posX = posY = 0;
	colorOfStone = true;
}


Concave::~Concave()
{
}

bool Concave::checkWin() {
	int state;
	if (colorOfStone)
		state = 1;
	else
		state = 2;
	for (int i = 0; i < SIZE; i++) {
		for (int j = 0; j < SIZE; j++) {
			int buff = bord[i][j];

			if (buff == state && buff != 0) {

				int countStone = 1;

				if (checkRange(i + 1, j) && bord[i + 1][j] == buff) {
					countStone++;
					for (int k = 2; k < 5; k++) {

						if (checkRange(i + k, j) && bord[i + k][j] == buff) {
							countStone++;

						}
						else {

							break;
						}
					}
				}
				if (countStone == 5)
					return true;
				else
					countStone = 1;

				if (checkRange(i, j + 1) && bord[i][j + 1] == buff) {
					countStone++;
					for (int k = 2; k < 5; k++) {
						if (checkRange(i, j + k) && bord[i][j + k] == buff) {
							countStone++;

						}
						else {

							break;
						}
					}
				}
				if (countStone == 5)
					return true;
				else
					countStone = 1;

				if (checkRange(i + 1, j + 1) && bord[i + 1][j + 1] == buff) {
					countStone++;
					for (int k = 2; k < 5; k++) {
						if (checkRange(i + k, j + k) && bord[i + k][j + k] == buff) {
							countStone++;

						}
						else {

							break;
						}
					}
				}
				if (countStone == 5)
					return true;
				else
					countStone = 1;

				if (checkRange(i + 1, j - 1) && bord[i + 1][j - 1] == buff) {
					countStone++;
					for (int k = 2; k < 5; k++) {
						if (checkRange(i + k, j - k) && bord[i + k][j - k] == buff) {
							countStone++;

						}
						else {

							break;
						}
					}
				}
				if (countStone == 5)
					return true;
			}
		}
	}
	return false;
}

bool Concave::checkRange(int x, int y) {
	return !(x >= SIZE || x < 0 || y >= SIZE || y < 0);
}

bool Concave::changeBord(int x, int y, bool value) {

	if (checkRange(x, y))
		bord[x][y] = value ? 1 : 2;
	else
		return false;
	return true;
}

bool Concave::moveX(int direction) {
	if (posX + direction < 0 || posX + direction >= SIZE)
		return false;
	posX += direction;
	return true;
}

bool Concave::moveY(int direction) {
	if (posY + direction < 0 || posY + direction >= SIZE)
		return false;
	posY += direction;
	return true;
}

 

- ConcavePvP Class

 

 다음은 PvP모드를 담당하는 ConcavePvP클래스이다. 베타버전과 다른점이 있다면 사용자에게 키를 입력받아 착수위치를 받는 코드를 moveDir()이라는 메소드를 통해 따로 구현했다는 것이다. 다음은 소스코드이다.

 

- ConcavePvP.h

 

#include <conio.h>
#include "Concave.h"
#include "InfoWindow.h"

#pragma once
class ConcavePvP : Concave, InfoWindow
{
private:
	void moveDir();
public:
	ConcavePvP();
	~ConcavePvP();
	void playConcave();
};

 

- ConcavePvP.cpp

 

#include "ConcavePvP.h"

ConcavePvP::ConcavePvP()
{
	printInfo();
	printBord();
}

ConcavePvP::~ConcavePvP()
{
}

void ConcavePvP::moveDir() {
	while (true) {
		int dir;
		dir = getch();

		switch (dir) {

		case 'w': moveX(-1);
			break;
		case 's': moveX(1);
			break;
		case 'a': moveY(-1);
			break;
		case 'd': moveY(1);
			break;
		case 'h': return;

		default:
			printfMessage("올바른 키를 입력해주세요!", getY() * 2, getX());
		}
		gotoXY(getY() * 2, getX());
	}
}

void ConcavePvP::playConcave() {

	gotoXY(0, 0);

	while (1) {

		if (getColor())
			printfMessage("흑돌의 순서입니다.", getY() * 2, getX());
		else
			printfMessage("백돌의 순서입니다.", getY() * 2, getX());

		moveDir();

		if (getColor())
			printf("○");
		else
			printf("●");

		changeBord(getX(), getY(), getColor());
		if (checkWin()) {
			if (getColor())
				printfMessage("흑돌이 승리하였습니다.", getY() * 2, getX());
			else
				printfMessage("백돌이 승리하였습니다.", getY() * 2, getX());
			return;
		}
		reverseColor();
	}
}

 

 조금 특별한 점이 있다면, InfoWindow, Concave클래스 두개를 다중상속 받았다는 점이다. 게임을 진행하면서 사용자의 편의를 위해 중간중간 안내문을 출력하는데, 직접적인 출력을 위해 playConcave()메소드 안에서 InfoWindow메소드를 직접 호출해야하기 때문이다.

 

3. 결어

 

 앞으로 진행할 일은 AI 모드를 작성하는 것이다. 기간안에 완성하여 꼭 AI 모드를 이번 특강 프로젝트 결과물로 제출했으면 좋겠다. 사실 복습을 위한 목적도 있지만 스스로의 동기부여를 위해 동계특강을 신청했기 때문이다.

반응형