C#문법 종합반의 과제로 나온 2번째 문제, Tic Tac Toe를 내가 직접 구현한 내용이다. 이 코드는 어제 구현했지만, 정리는 하지 못한 관계로 오늘 작성한다.
목 차
코드 개요
틱택토는 Player1과 Player2가 번갈아가며 3 * 3의 보드에서 한 칸을 자신의 표식으로 물들이고, 최종적으로 먼저 한 줄 빙고를 만드는 사람이 이기는 게임이다. 추가적으로 비정상적인 입력(ex: 이미 입력한 부분을 다시 입력하거나 아무것도 입력하지 않고 Enter를 칠 때 등.)시 패널티로 차례가 상대에게 넘어간다.
우선, 내가 만든 코드에는 전역에서 선언된 변수가 하나 있다.
//틱택토 보드의 각 칸에 들어가는 값에 대한 배열.
static char[] boardArr = { '1', '2', '3', '4', '5', '6', '7', '8', '9' };
처음에는 칸의 번호가 들어가지만, 플레이어 중 하나가 칸을 선택할 시 이 배열에서 해당하는 요소는 'O' 또는 'X'로 바뀐다.
또, 코드에는 Main을 제외하고 3개의 메서드가 있다.
/*틱택토 보드 출력 함수*/
static void PrintBoard(int turn);
/*현재 보드의 상태*/
static bool isFull(char input);
/*승리조건 검사*/
static bool BoardCheck();
Main 함수
#선언된 변수
int turn = 2; //차례: 플레이어 번호.
char inputChar; //입력받은 값.
bool isChar; //입력 유효성 검사.
int validInputCount = 0; //유효 입력 카운트.
- turn: int 형식으로 받아오는 플레이어 번호.
- 보드 출력 시 누구의 차례인지 전달 시.
- 보드의 숫자를 'O' 또는 'X'의 기호로 대체해야 할 때.
- 게임 종료 후 turn 값에 따라 승리한 플레이어 판별.
- inputChar: Console.ReadLine()을 통해 입력받은 값을 저장하는 공간.
- isChar: 입력하면 안되는 범위가 입력되는 것을 1차적으로 막아줌.
- validInputCount: 정상범위 값이 입력되었을 때 1을 추가함.
- 보드가 꽉 찼는지(==9) 확인.
#do~while문을 통한 게임 진행.
do
{
/* turn값 토글: 블럭 맨앞에 만듦으로써 do문 안의 모든 turn이 같은 값을 갖도록 함.*/
if (turn == 1) turn = 2;
else turn = 1;
PrintBoard(turn); //보드 새로고침.
/*사용자로부터 문자를 입력받기.*/
Console.Write("1~9 사이의 수 입력: ");
isChar = char.TryParse( Console.ReadLine(), out inputChar);
if (!isChar)
{
inputChar = '0'; //강제로 문자치환.
}
if (isFull(inputChar)) //isFull의 return 참조.
{
Console.WriteLine("잘못된 입력입니다. 1 ~ 9를 입력해주세요.");
Console.ReadLine(); //<1>
}
else
{
if (inputChar == boardArr[(int)inputChar - 49]) //<2>
{
if (turn == 1) boardArr[(int)inputChar - 49] = 'X';
else boardArr[(int)inputChar - 49] = 'O';
validInputCount++; //boardArr가 성공적으로 치환됨.
}
}
}
while (!BoardCheck() && !(validInputCount == 9)); //<3>
<1>: 잘못된 입력~ 이라는 출력 이후, Enter키를 한 번 더 입력해야 게임을 계속 진행할 수 있도록 해준다. 이렇게 하는 이유는 while문이 계속해서 진행될 때 Printboard 함수에 의해 Console.Clear()이 진행되면서 사용자가 이 출력을 확인할 수 없어지기 때문이다.
<2>: boardArr의 대괄호 안에서 -49를 해주는 이유는 inputChar을 int로 바꾸면 '1' ~ '9'의 아스키코드 값인 49 ~ 57가 되기 때문이다. 또한 '1'은 배열의 맨 앞, 즉 0번 칸에 저장되기 때문에 - 48에 1을 추가적으로 빼주어야 한다.
<3>: BoardCheck()가 false인 동시에 validInputCount가 9가 아니어야 while문은 계속된다.
BoardCheck는 승리조건이 감지되었을 때 true를 반환한다.
validInputCount는 0부터 시작해 증가하기만 하며, 9가 되는 순간 while문을 탈출하기에 그 이상으로 증가할 방법이 없다.
#게임 종료
PrintBoard(turn);
if (BoardCheck())
Console.WriteLine($"\n플레이어{turn}의 승리입니다.");
else
Console.WriteLine("\n비겼습니다.");
이 부분은 별게 없다. 그저 PrintBoard로 콘솔창을 마지막으로 정리하고, 승리조건을 판별해 무승부인지 아니면 특정 플레이어의 승리인지 판별해 그 결과를 출력한다.
void PrintBoard(int turn);
말 그대로 틱택토 보드를 출력하는 함수이다.
static void PrintBoard(int turn)
{
Console.Clear(); //<1>콘솔 초기화.
//<2>
Console.WriteLine("플레이어1: X 와 플레이어2: O"); //규칙 설명.
Console.WriteLine($"\n플레이어{turn}의 차례\n"); //현재 차례.
/*<3> 보드 출력.*/
Console.WriteLine(" | |");
Console.WriteLine($" {boardArr[0]} | {boardArr[1]} | {boardArr[2]}");
Console.WriteLine(" | |");
Console.WriteLine("-----------------");
Console.WriteLine(" | |");
Console.WriteLine($" {boardArr[3]} | {boardArr[4]} | {boardArr[5]}");
Console.WriteLine(" | |");
Console.WriteLine("-----------------");
Console.WriteLine(" | |");
Console.WriteLine($" {boardArr[6]} | {boardArr[7]} | {boardArr[8]}");
Console.WriteLine(" | |");
}
<1> 맨 위에서 콘솔 초기화를 해줌으로써 한 화면에 하나의 보드만 나오는 깔끔한 UI를 유지한다.
<2> 현재 보드에 대한 간단한 설명이다. 규칙과 현재 차례를 사용자에게 알려준다.
<3> 보간 문자열을 사용하여 각 칸에 boardArr의 요소들을 넣어준다. 그 외의 출력은 출력 시 격자무늬를 생성한다.
bool isFull(char input);
이 함수는 2가지 용도로 사용된다.
1. 입력이 '1' ~'9' 사이인 동시에 과거에 입력된 적이 없는가?
2. 'O' 또는 'X'를 입력하지는 않았는가?
static bool isFull(char input) //<1>input
{
/*보드의 각 칸의 문자 확인.*/
foreach (char c in boardArr) //<2>
{
if (c == input) //<3> input이 '1' ~ '9'일 때 찾기.
if (input != 'O' && input != 'X') //<4> 사용자가 O 또는 X를 잘못입력하는 것 방지.
return false; //board는 full이 아니다.
}
return true; //board가 full이거나 input이 잘못됐다.
}
<1> 입력받는 input은 main함수에서 사용자가 입력한 input과 같은 값이다.
<2> 배열의 모든 요소를 돌아야 하므로 foreach문을 사용했다. 만약 입력이 적절하다면, foreach문의 남은 반복은 return에 의해 생략된다.
<3> 여기서 위의 1번 용도가 활용이 된다. 만약 boardArr에 '1' ~ '9'가 아닌 다른 값(O / X)이 들어있거나, input이 '1' ~ '9'가 아닐 경우 여기서 1차적으로 걸러진다.
<4> 하지만 <3>의 방식으로 했을 때 input이 (O / X) 중 하나라면 if 문 안으로 들어올 수 있기 때문에 여기서 한 번 더 걸러준다.
bool BoardCheck();
특정 플레이어가 승리조건에 도달했는지 알려주는 함수이다.
1. while문을 탈출할 때.
2. while문을 탈출한 이유를 찾을 때.
이렇게 2번 사용된다.
static bool BoardCheck()
{
int[,] Clear = { {0, 1, 2}, {3, 4, 5}, {6, 7, 8},
{0, 3, 6}, {1, 4, 7}, {2, 5, 8},
{2, 4, 6}, {0, 4, 8} }; //<1> 승리조건을 나열한 2차원 배열.
/*승리할 수 있는 경우의 수만큼 반복.*/
for (int i = 0; i < 8; i++)
{
if ( boardArr[Clear[i, 0] ] == boardArr[Clear[i, 1] ] &&
boardArr[Clear[i, 0] ] == boardArr[Clear[i, 2] ] ) //<2>
{
return true; //승리 반환.
}
}
return false; //무효 반환.
}
<1> Clear[x, y]에서 x( = 작은 중괄호 통째로)는 게임 승리 조건의 번호, y( = 작은 중괄호 안의 값)는 승리하기 위해 필요한 배열의 index이다.
ex) boardArray[0] , boardArray[1], boardArray[2] 이 3개의 값이 같다면 보드에서 맨 윗줄이 모두 같은 모양이다.
<2> C#에서 내가 아는 한 3개의 항이 같은지 확인하는 별도의 방법은 없기에 우선 1번과 2번 값을 비교하고, 1번과 3번 값을 비교한 후 두 가지가 모두 참이라면 세 항이 모두 같다는 삼단논법을 통해 if문을 구성했다.
마무리
//추가: 장지원님-toggle이용(비트연산자)
//추가: 지승도님-3항연산자.
어제자 TIL에도 이에 대한 내용이 있었는데, 다른 부분은 거의 유사했지만 내가 이렇게 했으면 좋았겠다 하는 2가지를 뽑아왔다. 두 개 모두 turn을 바꾸는 방법에 대한 것인데, 지원님은 비트연산자 xor을 이용해서 turn을 바꿨고 승도님은 삼항연산자를 통해 바꿨다. 이 방법들 중 어느 것이라도 내가 쓴 if문의 활용보다 낫다고 생각되어 추가했다.
이 과제를 하면서 이번 C#문법 종합반의 심상치않은 난이도 상승 폭을 느꼈다. 이에 대한 체감이 훨씬 되는 것이 바로 다음주차 과제인 Snake 게임이다. 상상 이상으로 Snake 과제를 하는 데에 많은 시간을 소모해서 조급한 마음이 들었지만, 나만 어려워하는 것이 아니고 다른 사람들도 나와 매한가지라는 것을 알고 조금이나마 마음에 위안을 찾았다.
'내일배움캠프 > C#문법종합반' 카테고리의 다른 글
[TIL 24.05.05~06] 5주차 강의-알고리즘 기초, 정렬 알고리즘 (0) | 2024.05.05 |
---|---|
[TIL 24.04.29~05.05] 4주차 강의 필기노트 (0) | 2024.04.29 |
[TIL 24.04.28] 3주차 강의 필기노트 (0) | 2024.04.28 |
[TIL 24.04.23] 3주차 과제1: Snake Game (0) | 2024.04.23 |
[TIL 24.04.22] 1주차~2주차 강의 필기노트 (0) | 2024.04.22 |