틱택토란?
틱택토는 오목과 굉장히 유사한 게임입니다. 플레이어 2명에서 한명은 O, 한명은 X를 그리면서 같은 선상에 3개의 같은
그림을 만들면 승리하는 게임입니다. 플레이 방식이 간단한 만큼 무승부도 많이 나오는 특징을 가지고 있는 게임이기도 합니다.
오늘은 틱택토를 C# 콘솔환경에서 제작해보겠습니다.
실패코드1 (플레이어 턴 분배 실수)
namespace TickTackTo
{
internal class Program
{
static void Main(string[] args)
{
//게임이 끝났는지 체크
bool isEnd = false;
//어느곳에 그림을 그릴건지
int drawNum = 100;
//지금 몇번째 턴인지
int turnCount = 0;
//그림이 그려진 위치를 체크할 변수
int rows = 10;
int cols = 17;
int[,] usingPos = new int[rows, cols];
//누구의 차례인지 확인하는 bool
bool playerATurn = false;
bool playerBTurn = false;
//게임 판
string[,] map = new string[10, 17]
{
{"0","0","0","0","0","A","0","0","0","0","0","A","0","0","0","0","0"},
{"0","0","1","0","0","A","0","0","2","0","0","A","0","0","3","0","0"},
{"B","B","B","B","B","A","B","B","B","B","B","A","B","B","B","B","B"},
{"0","0","0","0","0","A","0","0","0","0","0","A","0","0","0","0","0"},
{"0","0","4","0","0","A","0","0","5","0","0","A","0","0","6","0","0"},
{"0","0","0","0","0","A","0","0","0","0","0","A","0","0","0","0","0"},
{"B","B","B","B","B","A","B","B","B","B","B","A","B","B","B","B","B"},
{"0","0","0","0","0","A","0","0","0","0","0","A","0","0","0","0","0"},
{"0","0","7","0","0","A","0","0","8","0","0","A","0","0","9","0","0"},
{"0","0","0","0","0","A","0","0","0","0","0","A","0","0","0","0","0"},
};
//배열 초기화
for(int i = 0; i< 10; i++)
{
for(int j = 0; j<17; j++)
{
usingPos[i, j] = 100;
}
}
do
{
if (turnCount % 2 == 0)
{
Console.WriteLine("플레이어 1: X와 플레이어 2: O\n\n");
Console.WriteLine("플레이어 1의 차례\n\n");
playerATurn = true;
playerBTurn = false;
}
else
{
Console.WriteLine("플레이어 1: X와 플레이어 2: O\n\n");
Console.WriteLine("플레이어 2의 차례\n\n");
playerBTurn = true;
playerATurn = false;
}
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 17; j++)
{
//사용된 부분이 있다면 기호로 변경
if (map[i, j] == usingPos[i,j].ToString())
{
if (playerATurn)
{
Console.Write("X");
}
else if (playerBTurn)
{
Console.Write("O");
}
}
else if (map[i, j] == "0")
{
Console.Write(" ");
}
else if (map[i, j] == "A")
{
Console.Write("|");
}
else if (map[i, j] == "B")
{
Console.Write("-");
}
else
{
Console.Write(map[i, j]);
}
}
Console.WriteLine();
}
Console.Write("\n\n");
Console.Write("어느곳에 그리겠습니까? 1 ~ 9 : ");
drawNum = int.Parse(Console.ReadLine());
SetUsingPos(drawNum);
turnCount++;
Console.Clear();
}
while (isEnd == false);
//사용 위치할당
void SetUsingPos(int _drawNum)
{
switch (_drawNum)
{
case 1:
usingPos[1, 2] = _drawNum;
break;
case 2:
usingPos[1, 8] = _drawNum;
break;
case 3:
usingPos[1, 14] = _drawNum;
break;
case 4:
usingPos[4, 2] = _drawNum;
break;
case 5:
usingPos[4, 8] = _drawNum;
break;
case 6:
usingPos[4, 14] = _drawNum;
break;
case 7:
usingPos[8, 2] = _drawNum;
break;
case 8:
usingPos[8, 8] = _drawNum;
break;
case 9:
usingPos[8, 14] = _drawNum;
break;
}
}
}
}
}
맵그리기
게임판을 특정 문자가 들어간 2차원 배열로 설정하였고. 판의 크기만큼 for문을 돌려 맵을 그렸습니다.
위 코드의 시연 영상은 아래와 같습니다.
실패 영상 1
for문을 사용하지 않고 간단하게 구현하는 방법이 떠올랐으나 쉬운길은 재미없는법, 사서 어려운 길을 택했습니다.
실패영상 코드해설
먼저 누구의 차례인지 확인할수 있는 bool값을 만들어
플레이어 1의 차례면 X, 플레이어 2의 차례면 O이 그려지도록 설계했습니다.
문제는 플레이어의 턴을 단순히 짝수턴이면 플레이어 1의 차례, 홀수턴이면 플레이어 2의 차례로 설정하다보니
ex)1,3,5,7.. -> 플레이어 1의 턴, ex)2,4,6,8... -> 플레이어 2의 턴
턴이 바뀔때마다 사용한 모든 칸이 해당 플레이어의 그림으로 전부 바뀌게 되었습니다.. 이거 골치아프네
맵을 그리고나면 플레이어에게 1~9의 숫자를 받아 drawNum 변수 안에 저장하였습니다.
저장된 drawNum을 SetUsingPos의 인자값으로 넘겨줘
해당 번호(1~9)가 있는 좌표값 = usingPos에 drawNum을 넘겨주게 됩니다.
for문을 돌다가 usingPos 좌표안에 들어있는 플레이어의 입력값(1~9)을 만나 O,X로 그리게 구현하였습니다.
문제는 해당 플레이어가 어디에 그렸는지 기억할 수 있는 정보 값을 어떻게 저장할건지인데..
플레이어에게 어디에 그릴것인지 입력을 받을 때. 어느 플레이어의 턴인지 기억해 두었다가.
for문이 돌때 해당 플레이어의 턴의 그린 그림이라면 O,X를 그리게 코드를 수정해보면 될 것 같다는 생각입니다.
실패 영상1 해결
using System.Reflection.PortableExecutable;
namespace TickTackTo
{
internal class Program
{
static void Main(string[] args)
{
//게임이 끝났는지 체크
bool isEnd = false;
//어느곳에 그림을 그릴건지
int drawNum = 100;
//지금 몇번째 턴인지
int turnCount = 0;
//그림이 그려진 위치를 체크할 변수
int rows = 10;
int cols = 17;
int[,] usingPos = new int[rows, cols];
//누구의 차례인지 확인하는 bool
bool playerATurn = false;
bool playerBTurn = false;
//해당 위치에 어느 플레이어가 그림을 그렸는지(P1 = 10, p2 = 20)
int[,] playerTurnInfo = new int[rows, cols];
//게임 판
string[,] map = new string[10, 17]
{
{"0","0","0","0","0","A","0","0","0","0","0","A","0","0","0","0","0"},
{"0","0","1","0","0","A","0","0","2","0","0","A","0","0","3","0","0"},
{"B","B","B","B","B","A","B","B","B","B","B","A","B","B","B","B","B"},
{"0","0","0","0","0","A","0","0","0","0","0","A","0","0","0","0","0"},
{"0","0","4","0","0","A","0","0","5","0","0","A","0","0","6","0","0"},
{"0","0","0","0","0","A","0","0","0","0","0","A","0","0","0","0","0"},
{"B","B","B","B","B","A","B","B","B","B","B","A","B","B","B","B","B"},
{"0","0","0","0","0","A","0","0","0","0","0","A","0","0","0","0","0"},
{"0","0","7","0","0","A","0","0","8","0","0","A","0","0","9","0","0"},
{"0","0","0","0","0","A","0","0","0","0","0","A","0","0","0","0","0"},
};
//배열 초기화
for(int i = 0; i< 10; i++)
{
for(int j = 0; j<17; j++)
{
usingPos[i, j] = 100;
playerTurnInfo[i, j] = 100;
}
}
do
{
if (turnCount % 2 == 0)
{
Console.WriteLine("플레이어 1: X와 플레이어 2: O\n\n");
Console.WriteLine("플레이어 1의 차례\n\n");
playerATurn = true;
playerBTurn = false;
}
else
{
Console.WriteLine("플레이어 1: X와 플레이어 2: O\n\n");
Console.WriteLine("플레이어 2의 차례\n\n");
playerBTurn = true;
playerATurn = false;
}
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 17; j++)
{
//사용된 부분이 있다면 기호로 변경
if (map[i, j] == usingPos[i,j].ToString())
{
if (playerTurnInfo[i,j] == 10)
{
Console.Write("X");
}
else if (playerTurnInfo[i,j] == 20)
{
Console.Write("O");
}
}
else if (map[i, j] == "0")
{
Console.Write(" ");
}
else if (map[i, j] == "A")
{
Console.Write("|");
}
else if (map[i, j] == "B")
{
Console.Write("-");
}
else
{
Console.Write(map[i, j]);
}
}
Console.WriteLine();
}
Console.Write("\n\n");
Console.Write("어느곳에 그리겠습니까? 1 ~ 9 : ");
drawNum = int.Parse(Console.ReadLine());
SetUsingPos(drawNum);
turnCount++;
Console.Clear();
}
while (isEnd == false);
//사용 위치할당
void SetUsingPos(int _drawNum)
{
switch (_drawNum)
{
case 1:
usingPos[1, 2] = _drawNum;
//플레이어 1의 턴
if(playerATurn)
{
//해당 좌표에 10저장
playerTurnInfo[1, 2] = 10;
}
//플레이어 2의 턴
else if(playerBTurn)
{
//해당 좌표에 20저장
playerTurnInfo[1, 2] = 20;
}
break;
case 2:
usingPos[1, 8] = _drawNum;
if (playerATurn)
{
playerTurnInfo[1, 8] = 10;
}
else if (playerBTurn)
{
playerTurnInfo[1, 8] = 20;
}
break;
case 3:
usingPos[1, 14] = _drawNum;
if (playerATurn)
{
playerTurnInfo[1, 14] = 10;
}
else if (playerBTurn)
{
playerTurnInfo[1, 14] = 20;
}
break;
case 4:
usingPos[4, 2] = _drawNum;
if (playerATurn)
{
playerTurnInfo[4, 2] = 10;
}
else if (playerBTurn)
{
playerTurnInfo[4, 2] = 20;
}
break;
case 5:
usingPos[4, 8] = _drawNum;
if (playerATurn)
{
playerTurnInfo[4, 8] = 10;
}
else if (playerBTurn)
{
playerTurnInfo[4, 8] = 20;
}
break;
case 6:
usingPos[4, 14] = _drawNum;
if (playerATurn)
{
playerTurnInfo[4, 14] = 10;
}
else if (playerBTurn)
{
playerTurnInfo[4, 14] = 20;
}
break;
case 7:
usingPos[8, 2] = _drawNum;
if (playerATurn)
{
playerTurnInfo[8, 2] = 10;
}
else if (playerBTurn)
{
playerTurnInfo[8, 2] = 20;
}
break;
case 8:
usingPos[8, 8] = _drawNum;
if (playerATurn)
{
playerTurnInfo[8, 8] = 10;
}
else if (playerBTurn)
{
playerTurnInfo[8, 8] = 20;
}
break;
case 9:
usingPos[8, 14] = _drawNum;
if (playerATurn)
{
playerTurnInfo[8, 14] = 10;
}
else if (playerBTurn)
{
playerTurnInfo[8, 14] = 20;
}
break;
}
}
}
}
}
플레이어가 어느 위치에 그림을 그렸는지 정보를 저장하는 playerTurnInfo 변수를 만들어
SetUsingPos() 함수를 수정해 해당 좌표의 정보값에 p1의 턴이면 10, p2의 턴이면 20을 저장하게 설정해 해결했습니다.
이제 게임의 규칙만 코드에 적용하면 끝날것 같습니다.
틱택토 완성
완성된 틱택토플레이 영상입니다.
게임의 규칙
1. 한턴에 한번씩 플레이어가 번갈아가면서 원하는 번호에 그림을 그릴 수 있습니다.
2. 이미 그려진 곳에 그림을 그릴경우 입력을 다시 받습니다.
3. 1~9가 아닌 다른 번호를 입력받았을 경우에도 다시 입력을 받습니다.
4. 그림이 3개이상 연속될 경우 해당 그림의 플레이어가 승리합니다.
5. 모든 번호를 사용할때까지 승리한 플레이가 없을 경우 무승부 처리합니다.
여담
코드를 짜다보니 550줄까지 코드가 길어졌네요.
승리 조건을 파악하는 공식을 세우려다 반복문 내에서 공식화를 시키기 어려워서
case로 승리 조건을 하나씩 다 따져서 구현했습니다. 그닥 깔끔한 코드는 아니라고 생각하지만
생각했던것을 모두 구현했다는 점에 의의를 둬야겠습니다.
나중에 실력이 늘었을때 다시 수정해보는걸로해야겠네요 코드는 너무 길어서 첨부파일로 올리겠습니다!
중간에.. SetCursor를 활용하면 진짜 쉽게 만들 수 있는 프로그램이였네요.
순차적으로 반복문으로 그리는 방법이 아닌, Console.Write로 먼저 맵을 그리고
이후에 원하는 좌표에 SetCursor로 숫자만 넣어서.. 특정 입력을 받으면 해당 좌표에
숫자를 O 또는 X로 바꾸면 되는 간단한 과제였는데.. 어려운 길을 간것 같네요.
이번 기회를 프로그램을 설계할때 좀 더 고민을 해보고 시야를 넓게 가져봐야겠다는 생각이 듭니다..
'C#' 카테고리의 다른 글
C# Partial (2) | 2024.01.05 |
---|---|
C# Static (2) | 2024.01.04 |
C# Class (0) | 2024.01.04 |
C# JSON 사용 방법(직렬화, 역직렬화) (1) | 2024.01.03 |
C# 형변환 (Casting) (1) | 2023.11.30 |