[Unity] 2주차 틱택토 구현
2주차 과제에 text로 틱택토게임을 구현하라는 과제였는데 일반적인 배열로 판을 만드는게 아닌, 오늘 처음 배운 2차원 배열로 판을 만들고 이로 틱택토 게임을 하는것을 구현해 보겠다.
연습용으로 만드는 과제이기 때문에 잘 몰랐던 2차원 배열과 잘 못쓰던 do While을 사용하여 코드를 구현해 보았다.
1. 2차원 배열
2차원 배열은 행과 열로 이루어진 데이터 구조를 다루기에 적합한 배열의 한 종류다. 기본적인 코드는 아래와 같다.
int[,] map = new int[5, 5];
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 5; j++)
{
map[i, j] = i + j;
}
}
5, 5면 행이 5 열도 5칸인 표가 하나 만들어진다.
이를 이용해 3 x 3 틱택토 판을 만들어 구현하도록 하자.
2. 틱택토 구현
2-1. board와 플레이어 선언
플레이어는 X 를 놓는 플레이어와 O를 놓는 플레이어 둘 로 나뉜다. X 말을 가진 사람이 선으로 정한다.
보드는 3 X 3 보드이다.
static char[,] board = new char[3, 3];
static char currentPlayer = 'X';
// 보드 초기화
static void InitializeBoard()
{
for (int row = 0; row < 3; row++)
{
for (int col = 0; col < 3; col++)
{
board[row, col] = ' ';
}
}
}
위는 선언 및 보드판 초기화 코드이다. 게임이 끝나고 나면 항상 초기 보드판으로 돌려놔야 하기 때문에, 반복을 두번 돌려 3 X 3으로 만들고, 값을 ' ' 빈칸으로 줘서 초기화를 시킨다.
// 보드판 생성
static void DisplayBoard()
{
Console.Clear();
// 좌표 써놓기
Console.WriteLine(" 0 1 2");
for (int row = 0; row < 3; row++)
{
Console.Write(row + " ");
for (int col = 0; col < 3; col++)
{
// 이 좌표에 현재 currentPlayer의 좌표를 들고와서 찍어줌
Console.Write(board[row, col]);
if (col < 2)
{
Console.Write("|");
}
}
Console.WriteLine();
if (row < 2)
{
Console.WriteLine(" -+-+-");
}
}
}
보드판은 위와 같다. 사용자가 가로와 세로를 각각 입력하여 말을 두게 하는 방식인데, 가로 세로의 숫자가 헷갈릴 수 있기 때문에 가로 세로열에 맞춰 숫자로 좌표를 적어 놓기위해 가로 좌표를 먼저 쓴 후 반복문을 돌리는 식이다.
Console.Write(row + " "); 는 우측에 숫자를 쓰고 한칸 띄어서 숫자 옆에 "ㅣ" 표시가 들어갈 수 있도록 하기 위해 써넣었고, "ㅣ" 표시는 2개만 필요하기 때문에 if문으로 두 번만 출력했다.
-+-+-+ 로 가운데 판을 십자모양으로 만들었으며 위를 출력하면 아래 사진과 같은 판이 완성된다.
2-2. 게임 실행
플레이어가 행과 열 순으로 좌표를 입력해야 하므로 먼저 가로 세로값을 선언한 후 플레이어가 입력할 수 있게 Console.ReadLine().Split(' ')으로 한칸 띄우고 입력할 수 있게 한 후 두 값을 각각 담아준다. 여기서 do내에 줘서 무조건 실행 및 무한반복을 걸어둔 후 while 에서 0, 1 ,2 가 아닌 다른 수를 쓰면 탈출 못하게 끔 코드를 짰다.
//게임시 플레이어가 좌표
static void PlayerInputNum()
{
int row, col;
do
{
Console.Write($"플레이어 {currentPlayer}, 행과 열을 입력하세요 (예: 0 0, 주의!: 0 부터 2까지만 입력해주세요): ");
string[] input = Console.ReadLine().Split(' ');
row = int.Parse(input[0]);
col = int.Parse(input[1]);
} while (row < 0 || row >= 3 || col < 0 || col >= 3 || board[row, col] != ' ');
board[row, col] = currentPlayer;
}
그리고 만약 입력한 수가 0, 1, 2 에 맞다면, currenPlayer(X 또는 O)가 어느 좌표에 썼는지 보내준다.
2-3. 플레이어 교대
// 플레이어 교대
static void SwitchPlayer()
{
if (currentPlayer == 'X')
{
currentPlayer = 'O';
}
else
{
currentPlayer = 'X';
}
}
현재 플레이어가 X 면 O로 아니면 X로 바꾸는 식으로 플레이어를 교대해주는 코드이다. 그래야 X한번 O한번 찍힌다.
2-3. 승리 및 패배
// 승자가 생기거나, 무승부시 게임 종료
static bool IsGameOver()
{
return IsWinner() || IsBoardFull();
}
static bool IsWinner()
{
for (int i = 0; i < board.GetLength(0); i++)
{
//가로 또는 세로 승리
if (board[i, 0] == currentPlayer && board[i, 1] == currentPlayer && board[i, 2] == currentPlayer)
return true;
if (board[0, i] == currentPlayer && board[1, i] == currentPlayer && board[2, i] == currentPlayer)
return true;
}
// 대각선 승리
if (board[0, 0] == currentPlayer && board[1, 1] == currentPlayer && board[2, 2] == currentPlayer)
return true;
if (board[0, 2] == currentPlayer && board[1, 1] == currentPlayer && board[2, 0] == currentPlayer)
return true;
return false;
}
return이 true면 게임이 종료되게끔 코드를 짰다. 위 if문은 가로 또는 세로에 한 플레이어가 다 먹었을 시 true를 반환하고, 아래 if문은 대각선을 다 먹었을 시에 true를 반환해준다. 해당하지 않으면 false를 반환하여 isGameOver함수에 전달한다.
// 무승부
static bool IsBoardFull()
{
for (int row = 0; row < 3; row++)
{
for (int col = 0; col < 3; col++)
{
if (board[row, col] == ' ')
return false;
}
}
return true;
}
위는 무승부 함수이다. board[row, col]에 빈칸이 있다면 false고 아니라면 true다. 즉, 꽉 차면 무승부이기 때문에 판이 꽉 찼을때 false를 반환하는 코드이다.
3. 마무리 및 전체코드
이런식으로 간단한 방식으로 틱택토를 만들어봤다. 아래는 전체 코드이고, 게임 승리시 화면이다.
namespace _2_Homework2
{
internal class Program
{
static char[,] board = new char[3, 3];
static char currentPlayer = 'X';
static void Main(string[] args)
{
InitializeBoard();
while (true)
{
DisplayBoard();
PlayerInputNum();
if (IsGameOver())
{
DisplayBoard();
Console.WriteLine("게임 종료!");
break;
}
SwitchPlayer();
}
}
// 보드 초기화
static void InitializeBoard()
{
for (int row = 0; row < 3; row++)
{
for (int col = 0; col < 3; col++)
{
board[row, col] = ' ';
}
}
}
// 보드판 생성
static void DisplayBoard()
{
Console.Clear();
// 좌표 써놓기
Console.WriteLine(" 0 1 2");
for (int row = 0; row < 3; row++)
{
Console.Write(row + " ");
for (int col = 0; col < 3; col++)
{
Console.Write(board[row, col]);
if (col < 2)
{
Console.Write("|");
}
}
Console.WriteLine();
if (row < 2)
{
Console.WriteLine(" -+-+-");
}
}
}
//게임시 플레이어가 좌표
static void PlayerInputNum()
{
int row, col;
do
{
Console.Write($"플레이어 {currentPlayer}, 행과 열을 입력하세요 (예: 0 0, 주의!: 0 부터 2까지만 입력해주세요): ");
string[] input = Console.ReadLine().Split(' ');
row = int.Parse(input[0]);
col = int.Parse(input[1]);
} while (row < 0 || row >= 3 || col < 0 || col >= 3 || board[row, col] != ' ');
board[row, col] = currentPlayer;
}
// 승자가 생기거나, 무승부시 게임 종료
static bool IsGameOver()
{
return IsWinner() || IsBoardFull();
}
// 승자 함수
static bool IsWinner()
{
for (int i = 0; i < board.GetLength(0); i++)
{
//가로 또는 세로 승리
if (board[i, 0] == currentPlayer && board[i, 1] == currentPlayer && board[i, 2] == currentPlayer)
return true;
if (board[0, i] == currentPlayer && board[1, i] == currentPlayer && board[2, i] == currentPlayer)
return true;
}
// 대각선 승리
if (board[0, 0] == currentPlayer && board[1, 1] == currentPlayer && board[2, 2] == currentPlayer)
return true;
if (board[0, 2] == currentPlayer && board[1, 1] == currentPlayer && board[2, 0] == currentPlayer)
return true;
return false;
}
// 무승부
static bool IsBoardFull()
{
for (int row = 0; row < 3; row++)
{
for (int col = 0; col < 3; col++)
{
if (board[row, col] == ' ')
return false;
}
}
return true;
}
// 플레이어 선공 교체
static void SwitchPlayer()
{
if (currentPlayer == 'X')
{
currentPlayer = 'O';
}
else
{
currentPlayer = 'X';
}
}
}
}
4. 마지막 한 마디
막상 만들고나니 누가 이겼는지, 다른 키를 눌렀을 때 경고 등 아쉬운 부분이 있었다. 사용자 입장에서 생각하며 코드를 짜는 연습을 많이 해야겠다고 느꼈고, 제대로된 설계와 함께 만들어야 역시 더 퀄리티있는 게임이 나올 것 같다.