본문 바로가기

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

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

반응형

1. 개요

 

 방학 때, 교수님께서 따로 진행하시는 특강을 듣게 되었는데 최종 과제로 제출한 프로그램을 올려볼까 한다. C++ 14버젼 이상의 문법을 설명해주시는 특강이었는데 STL 자료구조에 대해 자세히 배울 수 있었다. 하루에 6시간씩 일주일에 걸친 수업이었는데 체력적으로 버티기는 힘들었지만 이런 세상이 있었나 싶었다 ㅎㅎ. 사실 OOP적인 프로그램을 작성해야 했지만(상속, 가상 클래스) 하룻밤 만에 만들 수 있는 프로그램을 OOP스럽게 디자인하는 것은 쉽지 않았다 ㅠㅠ 결국 CLASS문법과 STL중 하나인 'array'를 사용한 절차지향적인 프로그램이 되어버렸다 ㅠㅠ 아직은 베타 버전이지만, 보수하는 과정을 끊임없이 이 블로그에 올리고 싶다.

 

2.  주요코드

 

 사실 주요코드라고 하는 것도 부끄럽다. 반복문을 엄청 사용하였기 때문에 코드가 깔끔하지도 않다 ㅠㅠ.

 

- 오목 승패 구분방법

 

일단 오목에 대한 알고리즘을 설명해보자면 오목의 승패가 결정되는 경우는 가로, 세로로 5개의 돌이 있는경우, 오른쪽, 왼쪽 대각선으로 5개의 돌이 있는 경우이다. 그림으로 표현하면 다음과 같을 것이다. 

 오른쪽 방향과 위쪽 방향없이 왼쪽과 아래쪽만을 생각해도 되는지에 대한 의문을 가질 수 있지만, 내가 구현한 코드는 오목판을 배열로 사용해였다. 탐색을 왼쪽에서 아래로 진행하였기에 왼쪽으로 5개가 없다면, 오른쪽으로도 5개가 존재할 수 없다. 오른쪽도 탐색할 수는 있지만 아무래도 계산 자원을 낭비하는 것 같기도 해서 구현하지 않았다.

 

- 오목 클래스 구현 방식

 

 사실 오목을 프로그램상에서 표현하는 방법은 정말 다양하겠지만, 직관적으로 해석할 수 있고 코드를 작성하기에 나에게 편리하면 좋을것 같아 이차원 배열로 구현하였다. 우리가 착수를 하는 곳은 오목판의 선들이 교차하는 곳이고, 그것을 배열로 나타내면 좋을 것 같았다. 초기에 값을 0으로 초기화하고, 흑돌을 착수하는 경우는 위치에 1저장, 백돌을 착수하는 경우는 위치에 2를 저장하여 착수 여부를 판단했다. STL array에서 2차원 배열을 작성하는 방법에 대한 자료가 많은 것 같지는 않았다. 텐서 오버플로우에서 찾은 방법인데, 다음과 같은 문법으로 작성할 수 있다고 한다.

 

array<array<int, SIZE>, SIZE> (배열이름);

 

 여기서 SIZE는 자신이 만들 배열의 크기를 넣어주는 부분이다.

 

- 헤더파일

 

 사실 만드는 class마다 헤더파일을 넣는것이 좋지만, 이상한 에러가 발생하여, 한 헤더파일에 모든 클래스의 정의를 포함하였다. 다음은 내가 작성한 class들을 정의한 헤더파일 본문이다.

 

#pragma once
#include <Windows.h>
#include <cstdio>
#include <conio.h>
#include <array>
#include <cstdio>
#include <cstring>
#include <iostream>
#define SIZE 15
#define LOCATION 15

using namespace std;

class InfoWindow
{
private:
	int startLocationX;
	int startLocationY;

public:
	InfoWindow() { startLocationX = 35; startLocationY = 0; }
	void printInfo();
	void printfMessage(const char *msg, int originX, int originY);
};

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


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

class ControlGoStone
{
private:
	Concave concave;
	InfoWindow imformation;
	int locationOfX;
	int locationOfY;

public:
	ControlGoStone();
	~ControlGoStone();
	void printBord();
	void movingStone();
};

 

- class 구현파일

 

다음은 class의 메소드들을 정의한 코드이다. 아직 베타버전이지만, 추후에 잘 정리된 코드를 작성할 것이다.

 

#include "ControlGoStone.h"

//값일 때
// 1, true: 흑돌 
// 2, false: 백돌
// 

void gotoxy(int x, int y);

ControlGoStone::ControlGoStone()
{
	locationOfX = locationOfY = 0;
	//concave.SetColorOfStone(true);
	printBord();
	imformation.printInfo();
}

ControlGoStone::~ControlGoStone()
{
}

void ControlGoStone::printBord() {
	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("-┘");
}

void ControlGoStone::movingStone() {

	gotoxy(0, 0);
	bool put = false;
	
	while (1) {

		if (concave.getColor())
			imformation.printfMessage("흑돌의 순서입니다.", concave.getY() * 2, concave.getX());
		else
			imformation.printfMessage("백돌의 순서입니다.", concave.getY() * 2, concave.getX());
			
		int dir;
		dir = getch();
		bool state;

		

		switch (dir) {

		case 'w': state = concave.moveX(-1);
			break;
		case 's': state = concave.moveX(1);
			break;
		case 'a': state = concave.moveY(-1);
			break;
		case 'd': state = concave.moveY(1);
			break;
		case 'h': put = true;
			break;
		default: state = false;
			imformation.printfMessage("올바른 키를 입력해주세요!", concave.getY() * 2, concave.getX());
		}

		if (put) {
			if (concave.getColor())
				printf("○");
			else
				printf("●");

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

//concave Class--------------------------------------------------------------------------------------------------------------

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::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;
}

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::checkRange(int x, int y) {
	return !(x >= SIZE || x < 0 || y >= SIZE || y < 0);
}

void Concave::printValue() {
	for (int i = 0; i < SIZE; i++) {
		for (int j = 0; j < SIZE; j++)
			printf("%2d", bord[i][j]);
		printf("\n");
	}
}

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;
}

//InfoClass------------------------------------------------------------------------------------------

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 gotoxy(int x, int y) {
	COORD Pos;
	Pos.X = x;
	Pos.Y = y;
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), Pos);
}

 

- main 함수

 

 부끄럽지만, OOP적인 디자인은 절대 아니다.(코드를 조금만 봐도 아실것 같다.) 어찌보면 main()기능을 하는 class를 만들고 그 class메소드를 main함수에서 실행해주는 방식일 수 있다. 이 부분도 수정하고싶다.

 

#include "ControlGoStone.h"

int main(void) {
	ControlGoStone game;
	game.movingStone();
	Sleep(3000);
	return 0;
}

 

- 예시 실행결과

 

 이 프로그램을 직접 실행해보면 더 자세히 알 수 있지만, 코드만을 원하시는 분들을 위해 예시 실행결과를 올린다.


예시 실행결과


화면 구성은 게임이 진행되는 곳과, 진행 상황을 알려주는 화면판으로 구성을 하였다. 서로 다른 CLASS로 구성하였다.

 

 

3. 결어

 

 스스로에게 자극을 주기 위해, 앞으로 보완할 점을 작성해보았다.

 

1. 자동으로 착수를 해주는 알고리즘 작성

2. OOP 형식의 프로그램으로 수정

3. 조금 더 편안한 인터페이스 구성

4. GUI 프로그램 사용해보기

5. 클래스 선언에러 해결

 

 이 많은 것을 이룰 수 있을지는 모르겠지만...... 이 문제 하나하나를 해결해가는 과정을 이 블로그에 올렸으면 좋겠다.

반응형