-
틱택토 게임 콘솔로 구현하기C# 2023. 8. 17. 01:34
I. 개요
틱택토 게임을 콘솔로 구현하기 위해 메서드, 컬렉션, 조건문과 반복문에서 학습한 것을 활용할 것이다.
조건문과 반복문
I. 조건문 : 주어진 조건식의 결과에 따라 프로그램의 제어 흐름을 변경하는 제어문 1. if 문 if (조건) { } int playerScore = 80; if (playerScore >= 70) // !! playerScore 은 70보다 크므로, 실행문이 실행이 됨. { Co
temp-franc.tistory.com
컬렉션
컬렉션 : 동일 타입의 객체(데이터)를 여러 개 보관할 수 있는 클래스들의 모음. : 배열을 제외한 나머지 컬렉션은 주로, 제네릭 클래스를 활용 I. 배열 : 동일한 자료형의 값들이 고정된 크기에
temp-franc.tistory.com
메서드
I. 메서드 : 특정한 작업을 수행하기 위해 사용되는 독립적인 기능 단위 : 코드의 재사용성과 모듈화를 위해 사용. !! 메서드를 사용해야하는 이유 1. 재사용이 용이함. : 동일한 작업을 직접 반복
temp-franc.tistory.com
사전에 명확한 계획없이 코드를 작성하게 된다면, 아래와 같이 굉장히 지저분해진다.
더보기코드 전문..
// 틱택토 Console.WriteLine("틱택토 게임에 오신걸 환영합니다.\n\n"); string[,] field = new string[3, 3]; int[,] calField = new int[3, 3]; int[] calArray = new int[8]; for (int i = 0; i < field.GetLength(0); i++) { for (int j = 0; j < field.GetLength(1); j++) { field[i, j] = $"({i},{j})"; } } for (int i = 0; i < calField.GetLength(0); i++) { for (int j = 0; j < calField.GetLength(1); j++) { calField[i, j] = 0; } } Random random = new Random(); int OXNum = random.Next(0, 2); string playerChar; string computerChar; string order; int attempt = 0; if (OXNum == 0) { playerChar = " O "; computerChar = " X "; order = "선공"; } else { playerChar = "X "; computerChar = "O "; order = "후공"; } while (true) { if (attempt == 0) { Console.WriteLine($"당신은 {order}입니다.\n"); attempt += 1; // 후공일 시, 컴퓨터 턴. if (order == "후공") { field[1, 1] = computerChar; calField[1, 1] = -1; } } // calArray 정의 및 계산 for (int i = 0; i < calField.GetLength(1); i++) // 각 가로 줄의 합 { int sum = 0; for (int j = 0; j < calField.GetLength(0); j++) { sum += calField[i, j]; } calArray[i] = sum; } for (int j = 0; j < calField.GetLength(0); j++) // 각 세로 줄의 합 { int sum = 0; for (int i = 0; i < calField.GetLength(1); i++) { sum += calField[i, j]; } calArray[j + 3] = sum; } calArray[6] = calField[0, 0] + calField[1, 1] + calField[2, 2]; // 대각선 줄의 합 (왼쪽) calArray[7] = calField[0, 2] + calField[1, 1] + calField[2, 0]; // 대각선 줄의 합 (오른쪽) // 화면 출력 for (int i = 0; i < field.GetLength(0); i++) { for (int j = 0; j < field.GetLength(1); j++) { Console.Write(field[i, j] + " "); } Console.WriteLine('\n'); } // 좌표 입력 Console.WriteLine($"좌표를 입력해주세요. 입력할 때마다, {playerChar}가 표시될 것입니다. ex) 1 1 : \n\n"); string input = Console.ReadLine(); // 틀린 형식을 입력했을 시. while (input.Contains(" ") == false) { Console.WriteLine("올바른 좌표를 입력해주세요! "); input = Console.ReadLine(); } string[] coordinate = input.Split(' '); int row = int.Parse(coordinate[0]); int col = int.Parse(coordinate[1]); // 아닌 좌표를 입력했을 시 if ( ((row < 0) || (row > 2)) || ((col < 0) || (col > 2)) ) { Console.WriteLine("올바른 좌표를 입력해주세요."); continue; } // 화면에 반영 및 컴퓨터 차례 로직 구현 else { // 입력 field[row, col] = playerChar; calField[row, col] = 1; attempt++; // 입력 후, calField 및 calArray 최신화 // calArray 정의 및 계산 for (int i = 0; i < calField.GetLength(1); i++) // 각 가로 줄의 합 { int sum = 0; for (int j = 0; j < calField.GetLength(0); j++) { sum += calField[i, j]; } calArray[i] = sum; } for (int j = 0; j < calField.GetLength(0); j++) // 각 세로 줄의 합 { int sum = 0; for (int i = 0; i < calField.GetLength(1); i++) { sum += calField[i, j]; } calArray[j + 3] = sum; } calArray[6] = calField[0, 0] + calField[1, 1] + calField[2, 2]; // 대각선 줄의 합 (왼쪽) calArray[7] = calField[0, 2] + calField[1, 1] + calField[2, 0]; // 대각선 줄의 합 (오른쪽) // 필드 상황 표시 Console.WriteLine("\n\n좌표 반영\n"); for (int i = 0; i < field.GetLength(0); i++) { for (int j = 0; j < field.GetLength(1); j++) { Console.Write(field[i, j] + " "); } Console.WriteLine("\n"); } Console.WriteLine("\n\n컴퓨터 행동\n\n"); // 승리시 break if (calArray.Max() == 3) { Console.WriteLine("올ㅋ 승리하셨습니다."); break; } // attempt가 9번이라면, 무승부화면 추가. if (attempt == 9) { Console.WriteLine("용호상박..!"); break; } // 컴퓨터 행동 // 중간이 제일 유리하므로, 비어있으면 가져오기 if (calField[1,1] == 0) { row = 1; col = 1; field[row, col] = computerChar; calField[row, col] = -1; } else // 중간을 못 가져올 경우, { bool isAgain = false; // 가장 높은 합을 가진 인덱스를 찾음. int caseNum = Array.FindIndex(calArray, element => element == calArray.Max()); // -2 일시, 승리를 위해 마침표 찍어야 함. int finalNum = Array.FindIndex(calArray, element => element == calArray.Min()); if (calArray.Min() == -2) { caseNum = finalNum; Console.Write("잡았다. 승리의 실마리!"); } // // 인덱스에 따라 경우 나누기 if (caseNum < 3) { List<int> tempList = new List<int>(); for (int i = 0; i < 3; i++) { tempList.Add(calField[caseNum, i]); } if (tempList.Contains(0)) { int indexNum = tempList.FindIndex(element => element == 0); // 해당 좌표에 컴퓨터 표시 row = caseNum; col = indexNum; field[row, col] = computerChar; calField[row, col] = -1; } else { isAgain = true; } } else if (caseNum < 6) { List<int> tempList = new List<int>(); for (int i = 0; i < 3; i++) { tempList.Add(calField[i, caseNum-3]); } if (tempList.Contains(0)) { int indexNum = tempList.FindIndex(element => element == 0); // 해당 좌표에 컴퓨터 표시 // Console.WriteLine(indexNum); // row = indexNum; col = caseNum-3; field[row, col] = computerChar; calField[row, col] = -1; } else { isAgain = true; } } else if (caseNum == 6) { List<int> tempList = new List<int>(); for (int i=0; i < 3; i++) { tempList.Add(calField[i, i]); } if (tempList.Contains(0)) { int indexNum = tempList.FindIndex(element => element == 0); // 해당 좌표에 컴퓨터 표시 row = indexNum; col = indexNum; field[row, col] = computerChar; calField[row, col] = -1; } else { isAgain = true; } } else { List<int> tempList = new List<int>(); for (int i = 0; i < 3; i++) { tempList.Add(calField[i, 2-i]); } if (tempList.Contains(0)) { int indexNum = tempList.FindIndex(element => element == 0); // 해당 좌표에 컴퓨터 표시 row = indexNum; col = 2-indexNum; field[row, col] = computerChar; calField[row, col] = -1; } else { isAgain = true; } } if (isAgain) { List<int> countList = new List<int>(); for (int i = 0; i < calArray.Length; i++) { int count = 0; if (i < 3) { for (int j = 0; j < 3; j++) { if (calField[i, j] == 0) { count++; } } countList.Add(count); } else if (i < 6) { for (int j = 0; j < 3; j++) { if (calField[j, i - 3] == 0) { count++; } } countList.Add(count); } else if (i == 6) { for (int j = 0; j < 3; j++) { if (calField[j, j] == 0) { count++; } } countList.Add(count); } else { for (int j = 0; j < 3; j++) { if (calField[j, 2-j] == 0) { count++; } } countList.Add(count); } } int countMax = countList.Max(); if (countMax < 3) { caseNum = countMax; for (int i = 0; i < 3; i++) { if (calField[caseNum, i] == 0) { row = caseNum; col = i; field[row, col] = computerChar; calField[row, col] = -1; break; } } isAgain = false; } else if (countMax < 6) { for (int i = 0; i < 3; i++) { if (calField[i, caseNum-3] == 0) { row = i; col = caseNum-3; field[row, col] = computerChar; calField[row, col] = -1; break; } } isAgain = false; } else if (countMax == 6) { for (int i = 0; i < 3; i++) { if (calField[i, i] == 0) { row = i; col = i; field[row, col] = computerChar; calField[row, col] = -1; break; } } isAgain = false; } else { for (int i = 0; i < 3; i++) { if (calField[i, 2-i] == 0) { row = i; col = 2-i; field[row, col] = computerChar; calField[row, col] = -1; break; } } isAgain = false; } } // attempt ++ attempt++; // attempt가 9번이라면, 무승부화면 추가. if (attempt == 9) { Console.WriteLine("용호상박..!"); break; } // calArray 정의 및 계산 for (int i = 0; i < calField.GetLength(1); i++) // 각 가로 줄의 합 { int sum = 0; for (int j = 0; j < calField.GetLength(0); j++) { sum += calField[i, j]; } calArray[i] = sum; } for (int j = 0; j < calField.GetLength(0); j++) // 각 세로 줄의 합 { int sum = 0; for (int i = 0; i < calField.GetLength(1); i++) { sum += calField[i, j]; } calArray[j + 3] = sum; } calArray[6] = calField[0, 0] + calField[1, 1] + calField[2, 2]; // 대각선 줄의 합 (왼쪽) calArray[7] = calField[0, 2] + calField[1, 1] + calField[2, 0]; // 대각선 줄의 합 (오른쪽) //break 부분 졌을 때 if (calArray.Min() == -3) { Console.WriteLine("컴퓨터에게 패배한 허접~\n\n"); for (int i = 0; i < field.GetLength(0); i++) { for (int j = 0; j < field.GetLength(1); j++) { Console.Write(field[i, j] + " "); } Console.WriteLine("\n"); } break; } } } }
따라서, 해당 코드를 되짚어보면서, 개념을 정의하고, 코드를 짤 때 계획의 중요성에 대해 환기하고 가겠다.
II. 계획
틱택토 게임은 삼목이다.
틱택토
Google에서 플레이
www.google.com
콘솔창에서는 다음과 같은 기능이 필요하다.
1. 게임의 시작을 알리는 기능
2. 현재 게임 진행 화면을 출력하는 기능.
3. 사용자 차례에, 사용자가 좌표를 입력할 수 있게 하는 기능
4. 승부가 났거나, 더 둘 칸이 없을 때에 게임을 종료하는 기능코드로 해결해야 하는 문제는 다음과 같다.
1. 선공과 후공이 랜덤으로 나타나는 기능.
2. 게임 진행 화면을 표현하는 기능.
3. 사용자 차례에 입력한 좌표를 게임에 추가하는 기능.
4. 컴퓨터 차례에 컴퓨터가 그럴듯한 곳에 표식을 추가하는 기능.
5. 승부가 끝났음을 판단하는 기능.이를 주석을 활용하여 코드로 나타내면
// !! 1. 게임 시작 // !! 2. 선공과 후공 정하기 // !! 3. 게임 화면을 출력 // !!! . 배열을 활용하여 게임 화면을 파악. // !!! . 후공이라면, 컴퓨터가 선택한 곳까지 출력. // !! 4. 사용자에게 좌표를 입력받음. // !!! . 입력받은 좌표를 게임 화면에 반영. // !!! . 규칙이 필요함. 놓은 자리에 다시 놓는 것은 안됨. // !!! . 규칙이 필요함. 제대로된 좌표를 지정해야함. // !! 5. 컴퓨터가 그럴듯한 곳에 좌표를 입력함. // !!! . 컴퓨터의 선택 알고리즘. // 5 - 1. 중앙을 선점하려 해야함. // 5 - 2. 내가 놓은 곳을 1, 컴퓨터가 놓은 곳을 -1 로 하는 3*3 배열에서 // . 가장 합이 작은 곳 == 컴퓨터만 놓인 곳 // . 이를 활용해야 함. // 5 - 3. 틱택토의 승리를 위한 줄은 8줄임 // !!! . 컴퓨터의 선택 화면에 반영. // !! 6. 게임의 끝을 알리는 기능. // !!! . 결과에 따라 다른 화면 출력 // !!! . 무승부일시, 판단하는 법.
III. 계획 수행
1. 게임 시작
: 간단하게 화면에 출력하면 된다.
// !! 1. 게임 시작 Console.WriteLine("틱택토 게임에 오신걸 환영합니다.\n\n");
: 게임의 화면 및 상황을 3 * 3 배열을 활용하여 만든다.
static void Main(string[] args) { // !! 1. 게임 시작 Console.WriteLine("틱택토 게임에 오신걸 환영합니다.\n\n"); string[,] field = new string[3, 3]; // 화면 출력을 위한 Array int[,] calField = new int[3, 3]; // 게임 상황 확인을 위한 Array for (int i = 0; i < field.GetLength(0); i++) { for (int j = 0; j < field.GetLength(1); i++) { field[i, j] = 0; } } Board board = new Board(); board.GameBoard(field); // 화면 출력 명령 } // 게임 화면 구축을 위한 클래스 class Board { public void GameBoard(string[,] boardArray) { // 보드는 3 * 3 크기 Console.WriteLine(" | | "); Console.WriteLine(" {0} | {1} | {2} ", boardArray[0,0], boardArray[0, 1], boardArray[0, 2]); Console.WriteLine("_____|_____|_____"); Console.WriteLine(" | | "); Console.WriteLine(" {0} | {1} | {2} ", boardArray[1, 0], boardArray[1, 1], boardArray[1, 2]); Console.WriteLine("_____|_____|_____"); Console.WriteLine(" | | "); Console.WriteLine(" {0} | {1} | {2} ", boardArray[2, 0], boardArray[2, 0], boardArray[2, 2]); Console.WriteLine(" | | "); } }
>
!! string 타입 배열의 요소의 default 값이 ""이고, 이후 O 아니면 X 한 글자만 들어갈 것이므로 공백 한 칸이 필요함.
string[,] field = new string[3, 3]; // 화면 출력을 위한 Array for (int i = 0; i < field.GetLength(0); i++) { for (int j = 0; j < field.GetLength(1); i++) { field[i, j] = " "; } }
>
2. 선공과 후공 정하기
: 사용자가 선공일 지, 후공일 지, 내장되어 있는 Random 클래스의 Random 메서드를 활용하여 정한다.
// !! 2. 선공과 후공 정하기 Random random = new Random(); int OXNum = random.Next(0, 2);
: OXNum 은 0 혹은 1의 값을 갖게 된다.
: OXNum에 따라 선공과 후공에서 달라지는 변수 혹은 상황을 정해준다.
-> 선공, 후공에 따라 각기 다른 멘트로 안내한다.
-> 후공일 시, 컴퓨터가 한 곳을 먼저 선택한 후의 화면을, 선공일시 최초의 빈 화면을 보여준다.
-> 사용자의 표식과 컴퓨터의 표식을 정해준다.
if (OXNum == 0) { playerChar = "O"; computerChar = "X"; Console.WriteLine("당신은 선공입니다."); // 화면 출력 } else { playerChar = "X"; computerChar = "O"; Console.WriteLine("당신은 후공입니다."); // 컴퓨터가 1회 선택 // 화면 출력 }
!! 틱택토는 선공이 유리하다. 중앙을 선점할 수 있기 때문이다.
: 따라서, 컴퓨터로 하여금 항상 중앙이 비어있다면, 항상 선점하게 해야한다.
!! 또한, 게임이 끝날 때까지 내차례 -> 상대 차례 -> 내 차례 ... 이 반복되므로, 이를 while(true)로 반복시킴으로써 구현.
: 내 차례 -> 상대 차례를 반복시킬 것.
: 선공권이 랜덤이므로, 횟수를 알려주는 변수를 사용하여, 처음이 상대차례일 경우만 처리.
// !! 2. 선공과 후공 정하기 Random random = new Random(); int OXNum = random.Next(0, 2); if (OXNum == 0) { playerChar = "O"; computerChar = "X"; Console.WriteLine("당신은 선공입니다."); Console.WriteLine("당신의 표식은 O 입니다."); } else { playerChar = "X"; computerChar = "O"; Console.WriteLine("당신은 후공입니다."); Console.WriteLine("당신의 표식은 X 입니다."); } int attempt = 0; // 첫턴 구분용. while(true) { if (attempt == 0) { if (OXNum == 0) { board.GameBoard(field); Console.WriteLine($"\n\n 두고 싶은 곳의 좌표를 설정해 주세요 ex) 3 3 : ") } else { field[1, 1] = computerChar // 컴퓨터가 중앙에 놓기 board.GameBoard(field); Console.WriteLine($"\n\n 두고 싶은 곳의 좌표를 설정해 주세요 ex) 3 3 : ") } } }
>
3. 게임 화면 출력
!! 화면 출력의 경우, 멘트까지 동일하므로, 메서드로 통일 시킬 것.
!! 후공일 시, 게임 화면에 적용시킬 것.
class Board { public string Announcement(string[,] boardArray) { Console.WriteLine($"\n 두고 싶은 곳의 좌표를 설정해 주세요 ex) 1 1 : \n"); Console.WriteLine($"<현재 상황>\n"); GameBoard(boardArray); string input = Console.ReadLine(); return input; } } // Main() //.. 생략 while (true) { if (attempt == 0) { if (OXNum == 1) // 후공이라면.. { field[1, 1] = computerChar; // 컴퓨터가 중앙에 놓기 calField[1, 1] = -1; // 계산용 배열에 추가하기 } string input = board.Announcement(field); attempt += 1; } }
4. 입력받은 좌표 반영하기
: 입력받은 좌표를 화면과 계산 배열에 반영하는 것.
: 메서드의 반환 타입을 참조 형식으로 걸어서, 게임 화면을 나타내는 field 배열과 계산 배열인 calField를 참조하였다.
public void inputApply(string input, string playerChar, ref string[,] field, ref int[,] calField) { while (true) { int inputResult = int.TryParse(input, out inputResult) ? inputResult : -1; if ( (input.Contains(" ") == false) || (inputResult == -1) ) { Console.WriteLine("올바른 좌표를 입력해주세요!"); input = Console.ReadLine(); } string[] coordinate = input.Split(' '); int row = int.Parse(coordinate[0]); int col = int.Parse(coordinate[1]); if ((row < 0) || (row > 2) || (col < 0) || (col > 2)) // 아닌 좌표를 입력했을 시 { Console.WriteLine("올바른 좌표를 입력해주세요!"); input = Console.ReadLine(); coordinate = input.Split(' '); row = int.Parse(coordinate[0]); col = int.Parse(coordinate[1]); } else if ((calField[row, col] != 0)) { Console.WriteLine("중복된 좌표를 입력하셨습니다. 다시 입력해주세요!"); input = Console.ReadLine(); // 좌표 획득 coordinate = input.Split(' '); row = int.Parse(coordinate[0]); col = int.Parse(coordinate[1]); } else { // 배열에 적용 field[row, col] = playerChar; calField[row, col] = 1; // 화면 출력 Console.WriteLine("\n\n"); GameBoard(field); Console.WriteLine("\n\n컴퓨터 차례"); break; } } }
5. 컴퓨터의 선택
!! 중앙을 잡는 것은 중요하므로, 중앙이 없다면 항상 중앙을 선택하게 해야함.
// 컴퓨터 행동 // 중간이 제일 유리하므로, 비어있으면 가져오기 if (calField[1,1] == 0) { row = 1; col = 1; field[row, col] = computerChar; calField[row, col] = -1; }
!! 가로 각 줄의 합 3줄, 세로 각 줄의 합 3줄, 오른쪽 아래로 향하는 대각선 1줄, 왼쪽 위로 향하는 대각선 1줄 의 각 합을 요소로 하는 calArray 를 생성.
// calArray 정의 : index 0 ~ 2 : 각 가로의 합 index 3 ~ 5 : 각 세로의 합 index 6, 7 : 대각선 public int[] GetCalArray(ref int[,] calField) { int[] calArray = new int[8]; // calArray 정의 및 계산 for (int i = 0; i < calField.GetLength(1); i++) // 각 가로 줄의 합 { int sum = 0; for (int j = 0; j < calField.GetLength(0); j++) { sum += calField[i, j]; } calArray[i] = sum; } for (int j = 0; j < calField.GetLength(0); j++) // 각 세로 줄의 합 { int sum = 0; for (int i = 0; i < calField.GetLength(1); i++) { sum += calField[i, j]; } calArray[j + 3] = sum; } calArray[6] = calField[0, 0] + calField[1, 1] + calField[2, 2]; // 대각선 줄의 합 (왼쪽) calArray[7] = calField[0, 2] + calField[1, 1] + calField[2, 0]; // 대각선 줄의 합 (오른쪽) return calArray; }
!! 내가 놓은 자리를 1, 컴퓨터가 놓은 자리를 -1 로 두는 동일한 크기의 calField를 기준으로 생각해보면.
: 컴퓨터의 입장에서는 대각선을 포함한 연속된 줄이 3이 되는 걸 막아야하고,
: 플레이어 입장에서는 -3이 되어야하는 것을 막아야한다.
: 따라서, 승리가 확실시 되는 -2 가 되지 않는 이상, 3이 되는 것을 막는 방향으로 설계했다.
public void ComputerPlay(ref int[,] calField, ref string[,] field, string computerChar) { bool isDuplicated = false; // 중복 여부 확인 int[] calArray = GetCalArray(ref calField); int caseNum = Array.FindIndex(calArray, element => element == calArray.Max()); // 가장 높은 합을 가진 인덱스를 찾음. int finalNum = Array.FindIndex(calArray, element => element == calArray.Min()); // 가장 낮은 합을 가진 인덱스를 찾음. while (isDuplicated == false) { // finalNum이 -2일 때, 마저 이어서 게임을 승리한다. if (calArray.Min() == -2) { caseNum = finalNum; } // 중간값 비어있다면.. if (calField[1,1] == 0) { int row = 1; int col = 1; field[row, col] = computerChar; calField[row, col] = -1; break; } else { // caseNum의 index에 따라 경우 나누기 if (caseNum < 3) { List<int> tempList = new List<int>(); for (int i = 0; i < 3; i++) { tempList.Add(calField[caseNum, i]); } int type = 1; isDuplicated = DuplicateChecker(type, tempList, isDuplicated, computerChar, caseNum, ref calField, ref field); if (isDuplicated != true) { break; } else { isDuplicated = DuplicateLoop(isDuplicated, computerChar, caseNum, ref calField, ref field, calArray); break; } } else if (caseNum < 6) { List<int> tempList = new List<int>(); for (int i = 0; i < 3; i++) { tempList.Add(calField[i, caseNum - 3]); } int type = 2; isDuplicated = DuplicateChecker(type, tempList, isDuplicated, computerChar, caseNum, ref calField, ref field); if (isDuplicated != true) { break; } else { isDuplicated = DuplicateLoop(isDuplicated, computerChar, caseNum, ref calField, ref field, calArray); break; } } else if (caseNum == 6) { List<int> tempList = new List<int>(); for (int i = 0; i < 3; i++) { tempList.Add(calField[i, i]); } int type = 3; isDuplicated = DuplicateChecker(type, tempList, isDuplicated, computerChar, caseNum, ref calField, ref field); if (isDuplicated != true) { break; } else { isDuplicated = DuplicateLoop(isDuplicated, computerChar, caseNum, ref calField, ref field, calArray); break; } } else { List<int> tempList = new List<int>(); for (int i = 0; i < 3; i++) { tempList.Add(calField[i, 2 - i]); } int type = 4; isDuplicated = DuplicateChecker(type, tempList, isDuplicated, computerChar, caseNum, ref calField, ref field); if (isDuplicated != true) { break; } else { isDuplicated = DuplicateLoop(isDuplicated, computerChar, caseNum, ref calField, ref field, calArray); break; } } } } }
!! 위의 메서드에서 반복적인 작업은 또 다른 메서드로 대체했다.
public bool DuplicateChecker(int type, List<int> tempList, bool isDuplicated, string computerChar, int caseNum, ref int[,] calField, ref string[,]field) { if (tempList.Contains(0)) // 빈 값 찾기 { int indexNum = tempList.FindIndex(element => element == 0); int row; int col; switch (type) { case 1: row = caseNum; // 해당 좌표에 컴퓨터 표시 col = indexNum; field[row, col] = computerChar; calField[row, col] = -1; break; case 2: row = indexNum; col = caseNum - 3; field[row, col] = computerChar; calField[row, col] = -1; break; case 3: row = indexNum; col = indexNum; field[row, col] = computerChar; calField[row, col] = -1; break; case 4: row = indexNum; col = 2-indexNum; field[row, col] = computerChar; calField[row, col] = -1; break; } isDuplicated = true; return isDuplicated; } else // 빈 값이 없다면 다른 index(caseNum) 찾기 { isDuplicated = false; return isDuplicated; } }
!! 또한, 이미 놓여진 자리에 놓아서는 안되며, 그럴 경우 빈 곳(0)을 제일 많이 포함하는 줄의 요소를 선택하는 방법을 채택했다.
private bool DuplicateLoop(bool isDuplicated, string computerChar, int caseNum, ref int[,] calField, ref string[,] field, int[] calArray) { if (isDuplicated) { { List<int> countList = new List<int>(); // calArray 와 같은 방식으로 0인 갯수를 찾음. for (int i = 0; i < calArray.Length; i++) { int count = 0; if (i < 3) { for (int j = 0; j < 3; j++) { if (calField[i, j] == 0) { count++; } } countList.Add(count); } else if (i < 6) { for (int j = 0; j < 3; j++) { if (calField[j, i - 3] == 0) { count++; } } countList.Add(count); } else if (i == 6) { for (int j = 0; j < 3; j++) { if (calField[j, j] == 0) { count++; } } countList.Add(count); } else { for (int j = 0; j < 3; j++) { if (calField[j, 2 - j] == 0) { count++; } } countList.Add(count); } } int countMax = countList.Max(); caseNum = countMax; if (countMax < 3) { for (int i = 0; i < 3; i++) { if (calField[caseNum, i] == 0) { int row = caseNum; int col = i; field[row, col] = computerChar; calField[row, col] = -1; break; } } } else if (countMax < 6) { for (int i = 0; i < 3; i++) { if (calField[i, caseNum - 3] == 0) { int row = i; int col = caseNum - 3; field[row, col] = computerChar; calField[row, col] = -1; break; } } } else if (countMax == 6) { for (int i = 0; i < 3; i++) { if (calField[i, i] == 0) { int row = i; int col = i; field[row, col] = computerChar; calField[row, col] = -1; break; } } } else { for (int i = 0; i < 3; i++) { if (calField[i, 2 - i] == 0) { int row = i; int col = 2 - i; field[row, col] = computerChar; calField[row, col] = -1; break; } } } isDuplicated = false; return isDuplicated; } } else { isDuplicated = false; return isDuplicated; } }
6. 게임 끝내기.
! 플레이어가 한 줄을 이었을 경우, 플레이어의 승리로 게임을 끝내야함.
: CalArray의 합이 3 혹은 -3이 되자마자 게임을 끝냄.
: 아닌 경우, 9개의 배열을 모두 쓰면 게임을 끝냄.
public bool GameOver(ref int[,] calField, int attempt) { int[] calArray = GetCalArray(ref calField); int calMax = calArray.Max(); int calMin = calArray.Min(); if (calMax == 3) { Console.WriteLine("올ㅋ 플레이어 승리"); return true; } else if (calMin == -3) { Console.WriteLine("허~접~"); return true; } else { if (attempt == 9) { Console.WriteLine("무승부"); return true; } else { return false; } } }
! 이후, Main 함수에 추가한다.
//.. 생략 int attempt = 0; // 첫턴 구분용. bool isEnd = false; // 게임 끝내기용 while (true) { if (isEnd) { break; } if (attempt == 0) { if (OXNum == 1) // 후공이라면.. { field[1, 1] = computerChar; // 컴퓨터가 중앙에 놓기 calField[1, 1] = -1; // 계산용 배열에 추가하기 attempt += 1; } Console.WriteLine("<시작>\n"); board.GameBoard(field); string input = board.Announcement(field); board.inputApply(input, playerChar, ref field, ref calField); attempt += 1; } else { // 컴퓨터 작동 board.ComputerPlay(ref calField, ref field, computerChar); attempt += 1; Console.WriteLine("<컴퓨터>\n"); board.GameBoard(field); isEnd = board.GameOver(ref calField, attempt); if (isEnd) { break; // 루프 탈출 } // 플레이어 차례 string input = board.Announcement(field); input = board.inputApply(input, playerChar, ref field, ref calField); attempt += 1; Console.WriteLine("<플레이어>\n"); board.GameBoard(field); isEnd = board.GameOver(ref calField, attempt); if (isEnd) { break; // 루프 탈출 } } }
III. 마치며....
.... 하루종일 걸렸다.
또한, 뭔가 메서드를 썼늗네 오히려 늘어난 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ,,,,
1달 반 뒤에, C# 문법 공부를 개인적으로 더 한 후에 한번더 코드를 다시 만들어봐야겠다.,,,
코드 전문.
using System.ComponentModel.Design; namespace TicTacToe { internal class TicTacToe { static void Main(string[] args) { // !! 1. 게임 시작 Console.WriteLine("틱택토 게임에 오신걸 환영합니다.\n\n"); string[,] field = new string[3, 3]; // 화면 출력을 위한 Array for (int i = 0; i < field.GetLength(0); i++) { for (int j = 0; j < field.GetLength(1); j++) { field[i, j] = " "; } } int[,] calField = new int[3, 3]; // 게임 상황 확인을 위한 Array for (int i = 0; i < calField.GetLength(0); i++) { for (int j = 0; j < calField.GetLength(1); j++) { calField[i, j] = 0; } } Board board = new Board(); // !! 2. 선공과 후공 정하기 Random random = new Random(); int OXNum = random.Next(0, 2); string playerChar; string computerChar; if (OXNum == 0) { playerChar = "O"; computerChar = "X"; Console.WriteLine("당신은 선공입니다.\n"); Console.WriteLine("당신의 표식은 O 입니다.\n"); } else { playerChar = "X"; computerChar = "O"; Console.WriteLine("당신은 후공입니다.\n"); Console.WriteLine("당신의 표식은 X 입니다.\n"); } int attempt = 0; // 첫턴 구분용. bool isEnd = false; // 게임 끝내기용 while (true) { if (isEnd) { break; } if (attempt == 0) { if (OXNum == 1) // 후공이라면.. { field[1, 1] = computerChar; // 컴퓨터가 중앙에 놓기 calField[1, 1] = -1; // 계산용 배열에 추가하기 attempt += 1; } Console.WriteLine("<시작>\n"); board.GameBoard(field); string input = board.Announcement(field); board.inputApply(input, playerChar, ref field, ref calField); attempt += 1; } else { // 컴퓨터 작동 board.ComputerPlay(ref calField, ref field, computerChar); attempt += 1; Console.WriteLine("<컴퓨터>\n"); board.GameBoard(field); isEnd = board.GameOver(ref calField, attempt); if (isEnd) { break; } // 플레이어 차례 string input = board.Announcement(field); input = board.inputApply(input, playerChar, ref field, ref calField); attempt += 1; Console.WriteLine("<플레이어>\n"); board.GameBoard(field); isEnd = board.GameOver(ref calField, attempt); if (isEnd) { break; } } } } class Board { public void GameBoard(string[,] boardArray) { // 보드는 3 * 3 크기 Console.WriteLine(" | | "); Console.WriteLine(" {0} | {1} | {2} ", boardArray[0,0], boardArray[1, 0], boardArray[2, 0]); Console.WriteLine("_____|_____|_____"); Console.WriteLine(" | | "); Console.WriteLine(" {0} | {1} | {2} ", boardArray[0, 1], boardArray[1, 1], boardArray[1, 2]); Console.WriteLine("_____|_____|_____"); Console.WriteLine(" | | "); Console.WriteLine(" {0} | {1} | {2} ", boardArray[0, 2], boardArray[1, 2], boardArray[2, 2]); Console.WriteLine(" | | "); Console.WriteLine("\n\n"); } public bool GameOver(ref int[,] calField, int attempt) { int[] calArray = GetCalArray(ref calField); int calMax = calArray.Max(); int calMin = calArray.Min(); if (calMax == 3) { Console.WriteLine("올ㅋ 플레이어 승리"); return true; } else if (calMin == -3) { Console.WriteLine("허~접~"); return true; } else { if (attempt == 9) { Console.WriteLine("무승부"); return true; } else { return false; } } } public string Announcement(string[,] boardArray) { Console.WriteLine($"\n 두고 싶은 곳의 좌표를 설정해 주세요 ex) 1 1 : \n"); string input = Console.ReadLine(); return input; } public string inputApply(string input, string playerChar, ref string[,] field, ref int[,] calField) { int attempts = 0; while (true) { int inputResult = int.TryParse(input[0].ToString(), out inputResult) ? inputResult : -1; if ( (input.Contains(" ") == false) || (inputResult == -1) ) { Console.WriteLine("올바른 좌표를 입력해주세요!"); input = Console.ReadLine(); continue; } string[] coordinate = input.Split(' '); int row = int.Parse(coordinate[0]); int col = int.Parse(coordinate[1]); if ((row < 0) || (row > 2) || (col < 0) || (col > 2)) // 아닌 좌표를 입력했을 시 { Console.WriteLine("올바른 좌표를 입력해주세요!"); input = Console.ReadLine(); continue; } else if ((calField[row, col] != 0)) { Console.WriteLine("중복된 좌표를 입력하셨습니다. 다시 입력해주세요!"); input = Console.ReadLine(); continue; } else { // 배열에 적용 coordinate = input.Split(' '); row = int.Parse(coordinate[0]); col = int.Parse(coordinate[1]); field[row, col] = playerChar; calField[row, col] = 1; // 화면 출력 Console.WriteLine("\n\n"); GameBoard(field); Console.WriteLine("\n\n"); break; } } return input; } public int[] GetCalArray(ref int[,] calField) { int[] calArray = new int[8]; // calArray 정의 및 계산 for (int i = 0; i < calField.GetLength(1); i++) // 각 가로 줄의 합 { int sum = 0; for (int j = 0; j < calField.GetLength(0); j++) { sum += calField[i, j]; } calArray[i] = sum; } for (int j = 0; j < calField.GetLength(0); j++) // 각 세로 줄의 합 { int sum = 0; for (int i = 0; i < calField.GetLength(1); i++) { sum += calField[i, j]; } calArray[j + 3] = sum; } calArray[6] = calField[0, 0] + calField[1, 1] + calField[2, 2]; // 대각선 줄의 합 (왼쪽) calArray[7] = calField[0, 2] + calField[1, 1] + calField[2, 0]; // 대각선 줄의 합 (오른쪽) return calArray; } public void ArrayCheck(ref int[,] array) { for (int i = 0; i < array.GetLength(0); i++) { for (int j = 0; j < array.GetLength(1); j++) { Console.Write(array[i, j] + " , "); } Console.WriteLine('\n'); } } public bool DuplicateChecker(int type, List<int> tempList, bool isDuplicated, string computerChar, int caseNum, ref int[,] calField, ref string[,]field) { if (tempList.Contains(0)) // 빈 값 찾기 { int indexNum = tempList.FindIndex(element => element == 0); int row; int col; switch (type) { case 1: row = caseNum; // 해당 좌표에 컴퓨터 표시 col = indexNum; field[row, col] = computerChar; calField[row, col] = -1; break; case 2: row = indexNum; col = caseNum - 3; field[row, col] = computerChar; calField[row, col] = -1; break; case 3: row = indexNum; col = indexNum; field[row, col] = computerChar; calField[row, col] = -1; break; case 4: row = indexNum; col = 2-indexNum; field[row, col] = computerChar; calField[row, col] = -1; break; } isDuplicated = false; return isDuplicated; } else // 빈 값이 없다면 다른 index(caseNum) 찾기 { isDuplicated = true; return isDuplicated; } } private bool DuplicateLoop(bool isDuplicated, string computerChar, int caseNum, ref int[,] calField, ref string[,] field, int[] calArray) { if (isDuplicated) { { List<int> countList = new List<int>(); // calArray 와 같은 방식으로 0인 갯수를 찾음. for (int i = 0; i < calArray.Length; i++) { int count = 0; if (i < 3) { for (int j = 0; j < 3; j++) { if (calField[i, j] == 0) { count++; } } countList.Add(count); } else if (i < 6) { for (int j = 0; j < 3; j++) { if (calField[j, i - 3] == 0) { count++; } } countList.Add(count); } else if (i == 6) { for (int j = 0; j < 3; j++) { if (calField[j, j] == 0) { count++; } } countList.Add(count); } else { for (int j = 0; j < 3; j++) { if (calField[j, 2 - j] == 0) { count++; } } countList.Add(count); } } int countMax = countList.Max(); caseNum = countMax; if (countMax < 3) { for (int i = 0; i < 3; i++) { if (calField[caseNum, i] == 0) { int row = caseNum; int col = i; field[row, col] = computerChar; calField[row, col] = -1; break; } } } else if (countMax < 6) { for (int i = 0; i < 3; i++) { if (calField[i, caseNum - 3] == 0) { int row = i; int col = caseNum - 3; field[row, col] = computerChar; calField[row, col] = -1; break; } } } else if (countMax == 6) { for (int i = 0; i < 3; i++) { if (calField[i, i] == 0) { int row = i; int col = i; field[row, col] = computerChar; calField[row, col] = -1; break; } } } else { for (int i = 0; i < 3; i++) { if (calField[i, 2 - i] == 0) { int row = i; int col = 2 - i; field[row, col] = computerChar; calField[row, col] = -1; break; } } } isDuplicated = false; return isDuplicated; } } else { isDuplicated = false; return isDuplicated; } } public void ComputerPlay(ref int[,] calField, ref string[,] field, string computerChar) { bool isDuplicated = false; // 중복 여부 확인 int[] calArray = GetCalArray(ref calField); int caseNum = Array.FindIndex(calArray, element => element == calArray.Max()); // 가장 높은 합을 가진 인덱스를 찾음. int finalNum = Array.FindIndex(calArray, element => element == calArray.Min()); // 가장 낮은 합을 가진 인덱스를 찾음. while (isDuplicated == false) { // finalNum이 -2일 때, 마저 이어서 게임을 승리한다. if (calArray.Min() == -2) { caseNum = finalNum; } // 중간값 비어있다면.. if (calField[1,1] == 0) { int row = 1; int col = 1; field[row, col] = computerChar; calField[row, col] = -1; break; } else { // caseNum의 index에 따라 경우 나누기 if (caseNum < 3) { List<int> tempList = new List<int>(); for (int i = 0; i < 3; i++) { tempList.Add(calField[caseNum, i]); } int type = 1; isDuplicated = DuplicateChecker(type, tempList, isDuplicated, computerChar, caseNum, ref calField, ref field); if (isDuplicated != true) { break; } else { isDuplicated = DuplicateLoop(isDuplicated, computerChar, caseNum, ref calField, ref field, calArray); break; } } else if (caseNum < 6) { List<int> tempList = new List<int>(); for (int i = 0; i < 3; i++) { tempList.Add(calField[i, caseNum - 3]); } int type = 2; isDuplicated = DuplicateChecker(type, tempList, isDuplicated, computerChar, caseNum, ref calField, ref field); if (isDuplicated != true) { break; } else { isDuplicated = DuplicateLoop(isDuplicated, computerChar, caseNum, ref calField, ref field, calArray); break; } } else if (caseNum == 6) { List<int> tempList = new List<int>(); for (int i = 0; i < 3; i++) { tempList.Add(calField[i, i]); } int type = 3; isDuplicated = DuplicateChecker(type, tempList, isDuplicated, computerChar, caseNum, ref calField, ref field); if (isDuplicated != true) { break; } else { isDuplicated = DuplicateLoop(isDuplicated, computerChar, caseNum, ref calField, ref field, calArray); break; } } else { List<int> tempList = new List<int>(); for (int i = 0; i < 3; i++) { tempList.Add(calField[i, 2 - i]); } int type = 4; isDuplicated = DuplicateChecker(type, tempList, isDuplicated, computerChar, caseNum, ref calField, ref field); if (isDuplicated != true) { break; } else { isDuplicated = DuplicateLoop(isDuplicated, computerChar, caseNum, ref calField, ref field, calArray); break; } } } } } } } }