<예시> : 제너릭을 이용해 스택 만들기
using System; namespace Generic_Stack { internal class Program { class Stack<T> { private T[] elements; private int top; public Stack() { elements = new T[100]; top = 0; } public void Push(T item) { elements[top++] = item; } public T Pop() { return elements[--top]; } } static void Main(string[] args) { Stack<int> intStack = new Stack<int>(); intStack.Push(123); intStack.Push(456); intStack.Push(789); Console.WriteLine(intStack.Pop()); // Stack은 선입 후출 구조기 때문에 789 출력 } } }
<예시> : out, ref 키워드 사용 예시
using System; namespace ConsoleApp1 { internal class Program { static void Main(string[] args) { // out 키워드 사용 예시 void Divide(int a, int b, out int quotient, out int remainder) { quotient = a / b; remainder = a % b; } int quotient, remainder; Divide(9, 2, out quotient, out remainder); Console.WriteLine($"{quotient}, {remainder}"); // 몫은 4 나머지는 1 이므로 "4, 1" 출력 // ref 키워드 사용 예시 void Swap(ref int a, ref int b) { int temp = a; a = b; b = temp; } int x = 1, y = 9; Swap(ref x, ref y); Console.WriteLine($"{x}, {y}"); // 1과 9의 값을 바꿔주기 때문에 "9, 1" 출력 } } }
1. 값의 변경 가능성
ref 매개변수를 사용하면 메소드 내에서 해당 변수의 값을 직접 변경할 수 있다. 이는 예기치 않은 동작을 초래할 수 있으므로 주의가 필요하다.
2. 성능 이슈
ref 매개변수는 값에 대한 복사 없이 메소드 내에서 직접 접근할 수 있기 때문에 성능상 이점이 있다. 그러나 너무 많은 매개변수를 ref로 전달하면 코드의 가독성이 떨어지고 유지보수가 어려워질 수 있다. 따라서 적절한 상황에만 ref를 사용하는 것이 좋다.
3. 변수 변경 여부 주의
out 매개변수는 메소드 내에서 반드시 값을 할당해야 한다. 따라서 out 매개변수를 전달할 때 해당 변수의 이전 값이 유지되지 않으므로 주의해야 한다.
해당 문제는 Unity Quest를 진행하며 풀었던 문제와 동일하지만 복습 겸 다시 한번 풀어봤습니다.using System; using System.Threading; namespace CorrectNumber { internal class Program { static void Main(string[] args) { Random random = new Random(); int answer = random.Next(1,101); int count = 0; Console.WriteLine("****숫자 맞추기 게임****"); Console.WriteLine("(1~100) 사이의 숫자를 맞추는 게임입니다."); Console.WriteLine("사용자가 숫자를 입력하면 정답보다 큰지 작은지 알려드립니다."); Console.WriteLine(); while (true) { Console.Write("숫자를 입력 해주세요 (범위 : 1~100) : "); int number = int.Parse(Console.ReadLine()); count++; if(number > answer) { Console.WriteLine("숫자가 너무 큽니다! 더 작은 수를 입력해주세요."); } else if(number < answer) { Console.WriteLine("숫자가 너무 작습니다! 더 큰 수를 입력해주세요."); } else { Console.WriteLine($"정답입니다! 답 : {answer}, 시도 횟수 : {count}"); break; } Console.WriteLine(); } } } }
- System namespace에 존재하는 Random 클래스의 Next함수를 사용해서 정답인 answer 변수를 랜덤한 값으로 초기화 한다.
- Next()의 첫번째 매개변수는 최소값이고, 두번째 매개변수는 최대값인데 최대값은 자신을 포함하지 않는다는 것을 잊지말자. (ex.
random.Next(1,101)==> 1~100 )
using System; namespace ConsoleApp1 { internal class Program { static void Main(string[] args) { bool Check(char[,] c) { if (c[0, 0] == 'o' && c[1, 1] == 'o' && c[2, 2] == 'o' || c[0, 0] == 'x' && c[1, 1] == 'x' && c[2, 2] == 'x' || c[2, 0] == 'o' && c[1, 1] == 'o' && c[0, 2] == 'o' || c[2, 0] == 'x' && c[1, 1] == 'x' && c[0, 2] == 'x') { return true; } else if (c[0, 0] == 'o' && c[0, 1] == 'o' && c[0, 2] == 'o' || c[0, 0] == 'x' && c[0, 1] == 'x' && c[0, 2] == 'x' || c[1, 0] == 'o' && c[1, 1] == 'o' && c[1, 2] == 'o' || c[1, 0] == 'x' && c[1, 1] == 'x' && c[1, 2] == 'x' || c[2, 0] == 'o' && c[2, 1] == 'o' && c[2, 2] == 'o' || c[2, 0] == 'x' && c[2, 1] == 'x' && c[2, 2] == 'x') { return true; } else { return false; } } Console.WriteLine("<틱택토 게임>"); Console.WriteLine(); Console.WriteLine(" - - - "); Console.WriteLine("| x | | o |"); Console.WriteLine(" - - - "); Console.WriteLine("| | x | |"); Console.WriteLine(" - - - "); Console.WriteLine("| x | o | o |"); Console.WriteLine(" - - - "); Console.WriteLine(); Console.WriteLine("***********************************"); Console.WriteLine(); Console.WriteLine("틱택토 게임이란?"); Console.WriteLine("틱택토 게임은 두 명의 사람이 번갈아가며 말을 놓는 게임입니다."); Console.WriteLine("게임은 3 x 3 격자판에서 이루어지며 처음에는 모두 비어있습니다."); Console.WriteLine("두 사람은 자신의 턴에 각각 O 또는 X 말을 격자판에 놓을 수 있습니다."); Console.WriteLine("자신의 말로 가로, 세로, 대각선 방향으로 3칸을 잇게 되면 승리입니다."); Console.WriteLine("승부가 나지 않은 상태로 게임판이 가득 차면 무승부로 게임이 종료됩니다."); Console.WriteLine("첫 턴 플레이어 : o , 둘째 턴 플레이어 : x"); Console.WriteLine(); char[,] arr = { { ' ', ' ', ' ' }, { ' ', ' ', ' ' }, { ' ', ' ', ' ' } }; bool turn = false; int turnCount = 0; while (true) { if (turnCount == 9) { Console.WriteLine(); Console.WriteLine(" - - - "); Console.WriteLine($"| {arr[0, 0]} | {arr[0, 1]} | {arr[0, 2]} |"); Console.WriteLine(" - - - "); Console.WriteLine($"| {arr[1, 0]} | {arr[1, 1]} | {arr[1, 2]} |"); Console.WriteLine(" - - - "); Console.WriteLine($"| {arr[2, 0]} | {arr[2, 1]} | {arr[2, 2]} |"); Console.WriteLine(" - - - "); Console.WriteLine(); Console.WriteLine("무승부 입니다!"); break; } Console.WriteLine("***********************************"); Console.WriteLine(); Console.WriteLine("현재 상황"); Console.WriteLine(); Console.WriteLine(" - - - "); Console.WriteLine($"| {arr[0, 0]} | {arr[0, 1]} | {arr[0, 2]} |"); Console.WriteLine(" - - - "); Console.WriteLine($"| {arr[1, 0]} | {arr[1, 1]} | {arr[1, 2]} |"); Console.WriteLine(" - - - "); Console.WriteLine($"| {arr[2, 0]} | {arr[2, 1]} | {arr[2, 2]} |"); Console.WriteLine(" - - - "); Console.WriteLine(); Console.WriteLine(" - - - "); Console.WriteLine("| 1 | 2 | 3 |"); Console.WriteLine(" - - - "); Console.WriteLine("| 4 | 5 | 6 |"); Console.WriteLine(" - - - "); Console.WriteLine("| 7 | 8 | 9 |"); Console.WriteLine(" - - - "); Console.WriteLine(); while (true) { Console.Write("말을 놓을 위치를 정해주세요. (1 ~ 9) : "); int loc = int.Parse(Console.ReadLine()); int x = 0; int y = 0; switch (loc) { case 1: x = 0; y = 0; break; case 2: x = 0; y = 1; break; case 3: x = 0; y = 2; break; case 4: x = 1; y = 0; break; case 5: x = 1; y = 1; break; case 6: x = 1; y = 2; break; case 7: x = 2; y = 0; break; case 8: x = 2; y = 1; break; case 9: x = 2; y = 2; break; } if (arr[x, y] != ' ') { Console.WriteLine("이미 말이 놓여 있습니다. 다시 선택해주세요."); } else if(loc < 1 || loc > 9) { Console.WriteLine("격자판의 범위를 벗어났습니다. 다시 선택해주세요."); } else { if (!turn) { arr[x, y] = 'o'; } else { arr[x, y] = 'x'; } break; } } if (Check(arr)) { if (!turn) { Console.WriteLine("첫 번째 턴 플레이어가 승리하였습니다! ( o )"); Console.WriteLine(); Console.WriteLine(" - - - "); Console.WriteLine($"| {arr[0, 0]} | {arr[0, 1]} | {arr[0, 2]} |"); Console.WriteLine(" - - - "); Console.WriteLine($"| {arr[1, 0]} | {arr[1, 1]} | {arr[1, 2]} |"); Console.WriteLine(" - - - "); Console.WriteLine($"| {arr[2, 0]} | {arr[2, 1]} | {arr[2, 2]} |"); Console.WriteLine(" - - - "); } else { Console.WriteLine("두 번째 턴 플레이어가 승리하였습니다! ( x )"); Console.WriteLine(); Console.WriteLine(" - - - "); Console.WriteLine($"| {arr[0, 0]} | {arr[0, 1]} | {arr[0, 2]} |"); Console.WriteLine(" - - - "); Console.WriteLine($"| {arr[1, 0]} | {arr[1, 1]} | {arr[1, 2]} |"); Console.WriteLine(" - - - "); Console.WriteLine($"| {arr[2, 0]} | {arr[2, 1]} | {arr[2, 2]} |"); Console.WriteLine(" - - - "); } break; } turnCount++; turn = !turn; } } } }
- while문을 통해 게임이 끝날 때 까지 반복해주며 진행하는 것이 포인트이다. 게임이 끝나는 조건은 어느한 플레이어가 승리하거나(Check 함수를 통해 확인) 모든 격자판이 채워졌을 때(turnCount 변수가 9가 되는 시점)이다.
- Check 함수는 게임 진행 상황 데이터가 저장되어 있는 arr[,] 배열을 받아 가로, 세로, 대각선으로 동일한 말이 놓여 있는 지 판단하는 함수이다.
- 편의를 위해 텍스트로 UI를 제작하다보니 코드가 길어진 것 같다...
지금까지 과제에 대한 설명이 없는 줄 알고 제목만 보고 느낌대로 풀었는데, 알고보니 강의자료에 과제 설명이 상세히 나와 있었다!!! 지금부턴 과제의 설명을 보고 풀어야겠다...
<요구사항>
1. Snake 클래스를 만듭니다. 이 클래스는 뱀의 상태와 이동, 음식 먹기, 자신의 몸에 부딪혔는지 확인 등의 기능을 담당합니다.
2. FoodCreator 클래스를 만듭니다. 이 클래스는 맵의 크기 내에서 무작위 위치에 음식을 생성하는 역할을 합니다.
3. Main 함수에서 게임을 제어하는 코드를 작성합니다. 이 코드는 뱀의 이동, 음식 먹기, 게임 오버 조건 확인 등을 주기적으로 수행합니다.
4. 필요에 따라 추가적인 함수나 클래스를 작성하여 게임을 완성합니다.
using Microsoft.Win32.SafeHandles; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Xml.Linq; namespace ConsoleApp1 { class Program { static void Main(string[] args) { // 맵 테두리 표현 for (int i = 0; i < 80; i++) { Console.SetCursorPosition(i, 0); Console.Write('■'); Console.SetCursorPosition(i, 20); Console.Write('■'); } for (int i = 0; i < 20; i++) { Console.SetCursorPosition(0, i); Console.Write("■"); Console.SetCursorPosition(80, i); Console.Write("■"); } // 뱀의 초기 위치와 방향을 설정하고, 그립니다. Point p = new Point(4, 5, '*'); Snake snake = new Snake(p, 4, Direction.RIGHT); snake.Draw(); // 음식의 위치를 무작위로 생성하고, 그립니다. FoodCreator foodCreator = new FoodCreator(80, 20, '$'); Point food = foodCreator.CreateFood(); food.Draw(); int length = snake.body.Count; int score = 0; // 게임 루프: 이 루프는 게임이 끝날 때까지 계속 실행됩니다. while (true) { // 방향키를 통해 뱀이 이동할 방향 입력받기 switch (Console.ReadKey(true).Key) { case ConsoleKey.LeftArrow: snake.dir = Direction.LEFT; break; case ConsoleKey.RightArrow: snake.dir = Direction.RIGHT; break; case ConsoleKey.UpArrow: snake.dir = Direction.UP; break; case ConsoleKey.DownArrow: snake.dir = Direction.DOWN; break; } // 뱀이 음식을 먹었을 경우 if (snake.Eat(food)) { score++; food = foodCreator.CreateFood(); food.Draw(); } else { snake.Move(); } if (snake.IsHitWall() || snake.IsHitTail()) { break; } Thread.Sleep(1); // 게임 속도 조절 (이 값을 변경하면 게임의 속도가 바뀝니다) Console.SetCursorPosition(85, 0); Console.WriteLine($"점수 : {score}"); Console.SetCursorPosition(85, 2); Console.WriteLine($"몸 길이 : {snake.body.Count}"); } Console.SetCursorPosition(45, 22); Console.WriteLine("Game Over!"); } } public class Snake { public List<Point> body; public Direction dir { get; set; } // Snake 클래스 생성자 public Snake(Point _body, int _length, Direction _dir) { body = new List<Point>(); for (int i = 0; i < _length; i++) { Point p = new Point(_body.x, _body.y, _body.sym); body.Add(p); _body.x += 1; } dir = _dir; } // 뱀을 그려주는 메소드 public void Draw() { for (int i = 0; i < body.Count; i++) { body[i].Draw(); } } // 뱀을 입력받은 방향으로 이동해주는 메소드 public void Move() { Point head = NextPosition(dir); Point tail = body.First(); // 뱀의 이동방향으로 1칸 이동 body.Add(head); head.Draw(); body.Remove(tail); tail.Clear(); } public Point NextPosition(Direction d) { Point next = new Point(body.Last().x, body.Last().y, body.Last().sym); switch (d) { case Direction.LEFT: next.x += -1; next.y += 0; break; case Direction.RIGHT: next.x += 1; next.y += 0; break; case Direction.UP: next.x += 0; next.y += -1; break; case Direction.DOWN: next.x += 0; next.y += 1; break; } return next; } public bool Eat(Point food) { Point head = NextPosition(dir); if (head.IsHit(food)) { food.sym = '*'; body.Add(food); food.Draw(); return true; } else { return false; } } public bool IsHitWall() { Point head = body.Last(); if (head.x == 0 || head.x == 80 || head.y == 0 || head.y == 20) { return true; } return false; } public bool IsHitTail() { Point head = body.Last(); for (int i = 0; i < body.Count - 1; i++) { if (head.IsHit(body[i])) { return true; } } return false; } } public class FoodCreator { public int mapSizeX { get; set; } public int mapSizeY { get; set; } public char shape { get; set; } Random random = new Random(); // FoodCreator 클래스 생성자 public FoodCreator(int _x, int _y, char _shape) { mapSizeX = _x; mapSizeY = _y; shape = _shape; } // 랜덤한 위치에 음식 생성 public Point CreateFood() { Point food = new Point(random.Next(1, mapSizeX), random.Next(1, mapSizeY), shape); return food; } } public class Point { public int x { get; set; } public int y { get; set; } public char sym { get; set; } // Point 클래스 생성자 public Point(int _x, int _y, char _sym) { x = _x; y = _y; sym = _sym; } // 점을 그리는 메서드 public void Draw() { Console.SetCursorPosition(x, y); Console.Write(sym); } // 점을 지우는 메서드 public void Clear() { sym = ' '; Draw(); } // 두 점이 같은지 비교하는 메서드 public bool IsHit(Point p) { return p.x == x && p.y == y; } } // 방향을 표현하는 열거형입니다. public enum Direction { LEFT, RIGHT, UP, DOWN } }
<사용하게 될 함수>
Thread.Sleep()
Thread 클래스의 메소드 중 하나로, 특정 시간 동안 현재 실행 중인 스레드의 실행을 중지합니다.Thread.Sleep(1000)은 현재 스레드를 1초 동안 중지시킵니다.Thread.Sleep() 메소드로 일정 시간을 기다리면, 그 시간 동안 다른 작업(예: 사용자 입력 처리, 뱀 이동 처리 등)을 수행할 수 있습니다.Console.ReadKey()
Console 클래스의 메소드 중 하나로, 사용자로부터 키보드 입력을 받아옵니다.ConsoleKeyInfo 객체를 반환하며, 이 객체를 통해 어떤 키가 눌렸는지를 알 수 있습니다.Console.ReadKey(true)와 같이 호출하면, 사용자로부터의 키 입력을 화면에 표시하지 않습니다. 이것은 게임에서 사용자의 입력을 처리할 때 화면에 키 입력이 표시되는 것을 방지하기 위한 것입니다.금방 끝낼 수 있을 것 같았는데 정말 오래걸린 문제... 지금부터 기능 하나씩 어떻게 구현했는 지 알아보자.
- 벽에 부딪히면 GameOver가 되는 조건이 있었기 때문에 벽을 그릴 필요가 있었다. 그래서 for문을 돌며 Console.SetCursorPosition()을 통해 커서를 옮겨가며 '■' 기호를 그려 맵 테두리를 감싸주었다.
- Console.ReadKey()를 통해 사용자가 입력한 방향을 토대로 뱀의 Direction을 저장해주었다. 그리고 그 Direction으로 뱀이 다음에 이동할 방향을 정해주었다.
- Snake 클래스의 Move()와 NextPosition()은 가장 핵심적인 함수이다. 뱀을 직접적으로 움직이는 메소드인데, NextPosition()을 통해 다음 위치로 이동하고 Move() 함수를 통해 이동한 위치로 그려주는 로직이다.
- Snake 클래스의 Eat() 함수는 뱀이 음식을 먹는 판정을 구현한 함수이다. 뱀이 다음으로 움직일 장소에 음식이 존재하면 음식이 있는 자리에 뱀의 몸통을 그려넣어 주며 뱀의 길이를 늘려준 후, true를 반환하는 로직이다. 이 함수를 통해 Main문에서 음식을 먹었을 때, 점수를 올리고 음식을 새로 생성한다.
- 벽에 부딪혔을 때 GameOver되는 판정은 뱀의 머리가 벽의 x,y값과 일치하면 true를 반환하는 로직이고, 꼬리에 부딪혔을 때 GameOver되는 판정은 뱀의 머리가 머리를 제외한 모든 몸통 부분 중 하나라도 x,y값이 일치하면 true를 반환하는 로직이다.
- FoodCreator 클래스의 CreateFood() 함수는 음식의 위치를 Random 클래스의 Next() 함수를 통해 랜덤으로 지정해준 후 Point 형식으로 반환해주는 로직이다.