C++ 프로젝트1 체스

jayiuu1·2022년 7월 21일
2

`

체스의 기본적인 룰 https://www.chess.com/ko/tiesuwosuru (출처: www.chess.com)

https://github.com/jjw712/my_chess_0709.git

기본적인 프로그램 작동 순서

Board() // 게임판 초기상태로 생성

ChessDisplay() // 게임판의 현재 상태 출력

GetCommand() // 입력 커멘드에 따라 행동

MoveTo() // 가능한 입력의 경우 말 이동 후 ChessDisplay() 단계 부터 반복

main 함수

실제 플레이는 class ChessPlay 에서 실행


#include <iostream>
#include "Let_Play.h"
using namespace std;

/// Board() -> 게임판
/// 구조체 Pieces -> 각 말의 정보, 
/// MoveTo() -> 각각의 말들의 이동
/// display() -> 현재 게임판의 상태 표시
/// 
/// Board생성 후 매턴(command->move->display) 반복

int main() {
	ChessPlay Play = ChessPlay();
	
	Play.~ChessPlay();
	
	printf("main\n");
	return 0;
}

구현한 헤더파일

Let_Play.h

-> class ChessBoard 의 멤버함수들을 순서에 맞게 실행

#pragma once
#include"Board.h"

class ChessBoard;
class ChessPlay {
private :

public:
	ChessPlay();
	
	~ChessPlay();
};

Board.h

-> 이동 출력 등의 실제 기능 구현

#pragma once
#include<stdio.h>
typedef struct Piece {	// 각 말의 정보
	int type;	// 1 Pawn 2 Rock 3 Knight 4 bishop 5 Queen 6 King
	int team;	// 0 black 1 white
}Piece;

static Piece pBoard[10][10] = { 0, };	//게임판 정의
/* 0 1 2 3 4 5 6 7 8 9
0 -1-1-1-1-1-1-1-1-1-1
1 -1	            -1
2 -1                -1
3 -1                -1
4 -1                -1
5 -1                -1
6 -1                -1
7 -1                -1
8 -1                -1
9 -1-1-1-1-1-1-1-1-1-1
*/
class ChessBoard {
private:

	
public:
	ChessBoard(Piece _pBoard[][10]);
	Piece* ChessDisplay(Piece _pBoard[][10], int _turn);
	int MoveTo(int ax, int ay, int bx, int by, Piece _pBoard[][10]);
//	int GetCommand(Piece _pBoard[][10]);
	int GetCommand2(Piece _pBoard[][10], int* _turn);
	~ChessBoard() {

	}
};

멤버함수

ChessPlay() in Let_Play.cpp

1. 초기 플레이보드 생성 후 (화면 출력 -> 커멘드 입력) 게임 종료 혹은 재시작 입력까지 무한반복
2. 이동 성공시 게임의 턴수 증가

#include "Let_Play.h"
#include<iostream>
ChessPlay::ChessPlay() {
	ChessBoard Board = ChessBoard(pBoard);
	int turn = 1;
	while (1) {
		Board.ChessDisplay(pBoard,turn);
		int tmp = Board.GetCommand2(pBoard, &turn);
		if (tmp >= 1)
			turn++;
	}
}
ChessPlay::~ChessPlay() {

}

ChessBoard() in Board.cpp

1. 초기상태의 플레이보드 생성

  • 다소 길어보이나 _pBoard 로 정의된 플레이보드에 말들을 체스판의 초기 상태로 넣어주는 단순 반복 과정
#include <Windows.h>
#include "Board.h"

#define		_MAX(a, b) (((a) > (b)) ? (a) : (b))

ChessBoard::ChessBoard(Piece _pBoard[][10]){	// (종류, 팀)
	
	Piece out = { -1,-1 };
	Piece empty = { 0,-1 };
	Piece B_Pawn = { 1,0 };
	Piece W_Pawn = { 1,1 };
	Piece B_Rock = { 2,0 };
	Piece W_Rock = { 2,1 };
	Piece B_Knight = { 3,0 };
	Piece W_Knight = { 3,1 };
	Piece B_Bishop = { 4,0 };
	Piece W_Bishop = { 4,1 };
	Piece B_Queen = { 5,0 };
	Piece W_Queen = { 5,1 };
	Piece B_King = { 6,0 };
	Piece W_King = { 6,1 };
	for (int y = 0; y <= 9; y++) {
		for (int x = 0; x <= 9; x++) {
			_pBoard[y][x] =empty;
		}
	}
//	if (start == true) {	// 처음
		for (int i = 0; i <= 9; i++) {
			_pBoard[i][0] = out;
			_pBoard[i][9] = out;
			_pBoard[0][i] = out;
			_pBoard[9][i] = out;
		}
		for (int x = 1; x <= 8;x++) {
			_pBoard[2][x] = W_Pawn;
			_pBoard[7][x] = B_Pawn;
		}
		_pBoard[1][1] = W_Rock;
		_pBoard[1][8] = W_Rock;
		_pBoard[8][1] = B_Rock;
		_pBoard[8][8] = B_Rock;
		_pBoard[1][2] = W_Knight;
		_pBoard[1][7] = W_Knight;
		_pBoard[8][2] = B_Knight;
		_pBoard[8][7] = B_Knight;
		_pBoard[1][3] = W_Bishop;
		_pBoard[1][6] = W_Bishop;
		_pBoard[8][3] = B_Bishop;
		_pBoard[8][6] = B_Bishop;
		_pBoard[1][4] = W_King;
		_pBoard[1][5] = W_Queen;
		_pBoard[8][4] = B_King;
		_pBoard[8][5] = B_Queen;
		for (int i = 0; i <= 9; i++) {
			for (int j = 0; j <= 9; j++) {
				if (_pBoard[j][i].team == 0 && _pBoard[j][i].type == 0) {
					_pBoard[j][i] = empty;
				}
			}
		}
		
//	}
}

ChessDisplay() in Board.cpp

1. 현재 플레이보드의 상태 출력

  • 현재 플레이보드를 인자로 받아 각 말의 team, type 에 따라 색과 문자를 바꾸어 출력
  • SetConsoleTextAttribute() -> 콘솔창의 텍스트 색 바꿔주는 함수 팀간의 구별을 위함
  • 마찬가지로 길어보이나 단순 반복 작업

2. 각 팀의 킹의 생존여부 확인하며 킹이 없을 경우 팀의 패배처리

Piece* ChessBoard::ChessDisplay(Piece _pBoard[][10], int _turn) {
	
//	Sleep(100);
	system("cls");		
	int King[2] = { 0, };
	for (int y = 0; y <= 9; y++) {
		//		printf("42\n");
		for (int x = 0; x <= 9; x++) {
			//			printf("43\n");
			int team, type;
			type = _pBoard[y][x].type;
			team = _pBoard[y][x].team;

			if (type >= 1) {
				if (team == 0)
					SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 1);
				else if (team == 1)
					SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 14);
			}
			else {
				SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 15);
			}
			if (y == 0 || x == 0) {
				printf("%d  ", _MAX(y,x));
				continue;
			}
			switch (type) {
				case -1:
					printf("ㅣ ");
					break;
				case 0:
					printf(".. ");
					break;
				case 1:
					printf("Pa ");
					break;
				case 2:
					printf("Ro ");
					break;
				case 3:
					printf("Kn ");
					break;
				case 4:
					printf("Bi ");
					break;
				case 5:
					printf("Qu ");
					break;
				case 6:
					printf("Ki ");
					King[team]=1;
					break;
				default:
					printf("   ");
					
			}
		}

		printf("\n");
	}
	printf("Press Q to Exit\n");
	printf("Press R to Restart\n");
	printf("\nturn: %d", _turn);
	if (_turn % 2 == 1)
		printf(" White\n");
	else
		printf(" Black\n");
	if (King[0] == 0) {
		system("cls");
		printf("White Win!!\n");
		Sleep(1000);
		exit(0);
	}
	if (King[1] == 0) {
		system("cls");
		printf("Black Win!!\n");
		Sleep(1000);
		exit(0);
	}
	return *_pBoard;
}

GetCommand2() in Board.GetCommand2.cpp

1. Window.h, conio.h 의 _getch(), gotoXY() 를 이용해 키보드로 입력받음

  • 가독성과 편의를 위해 각 역할에 따른 아스키 코드 define
  • switch 문으로 방향키 이동

2. 'R' 입력의 경우 재시작, 'Q' 입력의 경우 게임 종료

  • 재시작시 turn 초기화 후 ChessBoard.ChessBoard() 호출 게임판 초기화
  • 게임 종료시 종료 메세지 출력

3. Moveto() 를 return 하여 이동 성공 여부를 반환

  • turn수 증감 계산 위함
#include "Board.h"
#include<Windows.h>
#include<conio.h>

using namespace std;

#define	UP		72
#define	DOWN	80
#define	LEFT	75
#define RIGHT	77
#define ENTER	13
#define QUIT	81
#define quit	113
#define RESTART	82
#define restart	114
void gotoXY(int x, int y) {
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD pos;
	pos.X = x;
	pos.Y = y;
	SetConsoleCursorPosition(handle, pos);
}

int ChessBoard::GetCommand2(Piece _pBoard[][10], int* _turn) {
	int ax=0, ay=0, bx=0, by=0;
	int input = 0;
	int x = 3;
	int y = 1;
//	if (input == 224)
//		input = _getch();
	if (*_turn % 2 == 0) {
		x = 3;
		y = 8;
	}
	while (input != ENTER) {
		gotoXY(x, y);
		input = _getch();
		if (input == 224)
			input = _getch();
		switch (input) {
		case UP:
			if (y > 0)
				y--;
			break;
		case DOWN:
			if (y < 10)
				y++;
			break;
		case RIGHT:
			if (x < 10*3)
				x+=3;
			break;
		case LEFT:
			if (x > 0)
				x-=3;
			break;
		case ENTER:
			ax = x / 3;
			ay = y;
			break;
		case RESTART: {
			system("cls");
			printf("RESTART\n");
			*_turn = 1;
			Sleep(1000);
			system("cls");
			ChessBoard RePlay = ChessBoard(_pBoard);
			RePlay.ChessDisplay(_pBoard,1);

			break;
		}
		case QUIT:
			system("cls");
			printf("...Quit The Game...\n");
			Sleep(1000);
			exit(0);
		}
	}
	input = 0;
	while (input != ENTER) {
		gotoXY(x, y);
		input = _getch();
		if (input == 224)
			input = _getch();
		switch (input) {
		case UP:
			if (y > 0)
				y--;
			break;
		case DOWN:
			if (y < 10)
				y++;
			break;
		case RIGHT:
			if (x < 10 * 3)
				x += 3;
			break;
		case LEFT:
			if (x > 0)
				x -= 3;
			break;
		case ENTER:
			bx = x / 3;
			by = y;
			break;
		case RESTART: {
			printf("RESTART\n");
			ChessBoard RePlay = ChessBoard(_pBoard);

			
			break;
		}

		case QUIT:
			system("cls");
			printf("...Quit The Game...\n");
			Sleep(1000);
			exit(0);
		}
	}

	return MoveTo(ax,ay,bx,by,_pBoard);
}

MoveTo() in Board.MoveTo.cpp

1. (ax,ay) 위치의 말이 ,(bx,by) 로 이동함을 의미

  • switch 문으로 (ax,ay) 좌표의 말의 종류에 따라 다른 함수를 호출

2. 6개의 말의 종류에 맞추어 6개의 함수 구현

  • 불가능한경우 return 0; 빈곳으로 이동할 경우 return 1; 상대말을 공격할 경우 return 2;

3. 이동이 가능한 경우 _move() 함수로 (ax,ay) 를 비우고 (bx,by) 에 (ax,ay) 의 정보를 넣음

void _move(int ax, int ay, int bx, int by, Piece _pBoard[][10]) {
	_pBoard[by][bx].type = _pBoard[ay][ax].type;
	_pBoard[by][bx].team = _pBoard[ay][ax].team;
	_pBoard[ay][ax].type = 0;
	_pBoard[ay][ax].team = -1;
}
#include"Board.h"
#include<algorithm>
#include<math.h>
#include<conio.h>
#include<Windows.h>
using namespace std;

int Pa(int team, int ax, int ay, int bx, int by,Piece _pBoard[][10]);
int Ro(int team, int ax, int ay, int bx, int by, Piece _pBoard[][10]);
int Kn(int team, int ax, int ay, int bx, int by, Piece _pBoard[][10]);
int Bi(int team, int ax, int ay, int bx, int by, Piece _pBoard[][10]);
int Qu(int team, int ax, int ay, int bx, int by, Piece _pBoard[][10]);
int Ki(int team, int ax, int ay, int bx, int by, Piece _pBoard[][10]);
void gotoXY(int x, int y);

int ChessBoard::MoveTo(int ax, int ay, int bx, int by, Piece _pBoard[][10]) {
	
//	Piece _pBoard[10][10] = ChessBoard::_pBoard;
	int team = _pBoard[ay][ax].team;
	int type =_pBoard[ay][ax].type;
	int tmp_B[10][10] = { 0, };	//0 불가능 1 가능 2 어택
	gotoXY(0, 13);
	if (ax < 1 || ax>8 || bx < 1 || bx>8 || ay < 1 || ay>8 || by < 1 || by>8) {
		printf("input error\n");
		return 0;
	}
	switch (type) {
	case -1:
		printf("empty");
		return 0;
		break;
	case 0:
		printf("empty");
		return 0;
		break;
	case 1:
		return Pa(team, ax, ay, bx, by,_pBoard);
		break;
	case 2:
		return Ro(team, ax, ay, bx, by, _pBoard);
		break;
	case 3:
		return Kn(team, ax, ay, bx, by, _pBoard);
		break;
	case 4:
		return Bi(team, ax, ay, bx, by, _pBoard);
		break;
	case 5:
		return Qu(team, ax, ay, bx, by, _pBoard);
		break;
	case 6:
		return Ki(team, ax, ay, bx, by, _pBoard);
		break;
	default:
		return 0;
		printf("   ");
	}
	
	return 0;
}

각 말의 기능 예시

Pawn

  • pawn 의 경우 첫 이동시 2칸, 공격시엔 대각선 이동 등의 예외사항을 고려해야 한다.
int Pa(int team, int ax, int ay, int bx, int by, Piece _pBoard[][10]) {
	if (team == 1) {	// White
		if (_pBoard[by][bx].type > 0) {
			if (ay + 1 == by && abs(ax - bx) == 1) {
				_move(ax, ay, bx, by, _pBoard);
				printf("attack\n");
				return 2;
			}
		}
		else if(ay == 2){
			if (ax == bx && (by - ay == 1 || (by - ay == 2 && _pBoard[ay + 1][by].type < 1))) {
				_move(ax, ay, bx, by, _pBoard);
				printf("move\n");
				return 1;
			}
			
		}
		else {
			if ((ax == bx && by - ay == 1)) {
				_move(ax, ay, bx, by, _pBoard);
				printf("move\n");
				return 1;
			}
		}
		printf("impossible\n");
		return 0;
	}
	else if(team == 0){		// Black
		if (_pBoard[by][bx].type > 0) {
			if (ay - 1 == by && abs(ax - bx) == 1) {
				_move(ax, ay, bx, by, _pBoard);
				printf("attack\n");
				return 2;
			}
		}
		else if (ay == 7) {
			if (ax == bx && (by - ay == -1 || (by - ay == -2 && _pBoard[ay - 1][by].type < 1))) {
				_move(ax, ay, bx, by,_pBoard);
				printf("move\n");
				return 1;
			}

		}
		else {
			if ((ax == bx && by - ay == -1)) {
				_move(ax, ay, bx, by, _pBoard);
				printf("move\n");
				return 1;
			}
		}
		printf("impossible\n");
		return 0;
	}
	return 0;
}	 //complete

Bishop

  • 절대값을 반환해주는 함수 abs() 를 이용하여 대각선 이동여부를 판별
int Bi(int team, int ax, int ay, int bx, int by, Piece _pBoard[][10]) {
	if (abs(ax - bx) != abs(ay - by)) {
		printf("impossible\n");
		return 0;
	}
	int N = abs(ax - bx);
	if (ax > bx && ay > by) {	//7
		for (int i = 1; i < N - 1; i++) {
			if (_pBoard[ay - i][ax - i].type >= 1) {
				printf("impossible\n");
				return 0;
			}
		}
	}
	if (ax > bx && ay < by) {	//1
		for (int i = 1; i < N - 1; i++) {
			if (_pBoard[ay + i][ax - i].type >= 1) {
				printf("impossible\n");
				return 0;
			}
		}
	}
	if (ax < bx && ay > by) {	//9
		for (int i = 1; i < N - 1; i++) {
			if (_pBoard[ay - i][ax + i].type >= 1) {
				printf("impossible\n");
				return 0;
			}
		}
	}
	if (ax < bx && ay < by) {	//3
		for (int i = 1; i <= N - 1; i++) {
			if (_pBoard[ay + i][ax + i].type >= 1) {
				printf("impossible\n");
				return 0;
			}
		}
	}
	if (_pBoard[by][bx].type <= 0) {
		printf("move\n");
		_move(ax, ay, bx, by, _pBoard);
		return 1;
	}
	else if (_pBoard[by][bx].team == team) {
		printf("same team impossible\n");
		return 0;
	}
	else if (_pBoard[by][bx].team != team && _pBoard[by][bx].team != -1) {
		printf("attack\n");
		_move(ax, ay, bx, by, _pBoard);
		return 2;
	}
	
	return 0;
}

추후에 추가할 수 있는 사항

1. 계승, 캐슬링, 양파상 등의 체스 특수 규칙
2. 흑팀 백팀 선택 여부
3. 스코어보드 혹은 타이머
..etc

총 정리

딱히 대단한 알고리즘이나 자료구조를 이용하지 않아도 충분히 구현 가능했다. 다만 체스의 경우 반드시 2명이 있어야 플레이 가능해 실제로 플레이 해보거나 디버깅시 어려움이 있었다. 다음 프로젝트는 혼자서도 플레이 가능한 주제로 잡는것이 좋겠다.

0개의 댓글