[내배캠] TIL 권장하는 TIL 작성법 + 스네이크 게임 과제 코드 + 블랙잭 과제 코드
10년도 더 전에 C로 하던걸 C#으로 다시 할 줄이야.... 별찍기 건너뛰고 이것부터 하는건 좀 아쉽다. 학생 때는 별찍기 하면서 육망성 만들고, 커서 움직이는거 배우자마자 삼각함수로 오각별 만들고 그랬는데 추억이다. 낄낄
*
**
***
****
*****
많은 학생들의 스트레스를 유발했던 과제 별찍기
같은 조에 반복문에 어려움을 느껴하는 조원이 있길래 별찍기로 연습해보라고 추천해줬다. 나는 고등학생 때 주구장창 별찍기만 한 달을 했었는데, 어느 순간 뇌가 맑아지는 느낌을 받으면서 신들린 것처럼 반복문이 쉬워졌다. 분명 이 때 뇌주름이 한 줄 더 생겼을 것이다.
권장하는 TIL 작성법
TIL을 잘 작성하는 방법에 대해 매니저 님이 특강을 해주셨다. 크게 4가지로 카테고리를 나눠 글을 작성하면 된다. 4가지 카테고리를 외우기 쉽게 '문시해알'로 말씀해주셨고, 이번 TIL부터 적용해보려고 한다.
문시해알이란?
문제점, 시도해본 것, 해결방법, 알게된 것의 첫 글자를 딴 것으로 TIL을 기승전결에 맞게 작성하라는 뜻 같다.
문시해알 양식대로 작성한 TIL. C#에서 비동기적인 키 입력 수행 방법
문제점
프로그램에서 입력을 받기 위해서는 '입력이 들어오기 전까지 실행을 멈춘다.'가 기본이다. 하지만 스네이크 게임은 키보드의 입력이 들어오던 말던 상관없이 뱀이 화면상에서 움직인다. 만약 입력을 받기 위해서 ReadKey()
함수를 사용한다면 입력 전까지 뱀은 움직이지 않을 것이다.
이 문제는 10년 전 고등학생 시절 C, C++ 으로 이미 해결해본 문제로, GetAsyncKeyState()
함수를 사용하면 입력 대기 없이 비동기적으로 키보드 입력을 받아올 수 있다. C#에서 GetAsyncKeyState()
와 같은 역할을 하는 함수가 무엇인지 알아내야 했다.
시도해본것
GetAsyncKeyState()
처럼 키의 상태를 받아오는 함수가 있는지 찾아봤다.
해결방법
Console.KeyAvailable
로 키보드가 입력이 된 상태인지 판별한 다음에 입력을 받으면 입력 대기 없이 루프를 실행할 수 있다. 아래는 스네이크 게임의 입력을 받는 코드다.
while (true) { if(Console.KeyAvailable) { ConsoleKeyInfo keyInfo = Console.ReadKey();
if (keyInfo.Key == ConsoleKey.LeftArrow && snake.direction != Direction.RIGHT)
{
snake.direction = Direction.LEFT;
}
else if (keyInfo.Key == ConsoleKey.UpArrow && snake.direction != Direction.DOWN)
{
snake.direction = Direction.UP;
}
else if (keyInfo.Key == ConsoleKey.RightArrow && snake.direction != Direction.LEFT)
{
snake.direction = Direction.RIGHT;
}
else if (keyInfo.Key == ConsoleKey.DownArrow && snake.direction != Direction.UP)
{
snake.direction = Direction.DOWN;
}
}
//생략
알게된것
C# 에서는 입력이 들어오는 상태인지 아닌지 먼저 판별하는 방식으로 입력 대기를 없앨 수 있다는 차이점을 알았다. GetAsyncKeyState()
와 다르게 매 틱마다 키 하나하나의 상태를 받아오는 것보다 전체적으로 키 입력 상태인지 확인하는 것이 쓸모없는 코드 실행을 줄일 수 있는 것 같아 더 마음에 들었다.
스네이크 게임 코드
using System; using System.Collections.Generic; using System.Threading;
class Program
{
static void Main(string\[\] args)
{
for(int i=0; i<21; i++)
{
Console.SetCursorPosition(81, i);
Console.Write("│");
}
Console.SetCursorPosition(0, 21);
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();
while (true)
{
if(Console.KeyAvailable)
{
ConsoleKeyInfo keyInfo = Console.ReadKey();
if (keyInfo.Key == ConsoleKey.LeftArrow && snake.direction != Direction.RIGHT)
{
snake.direction = Direction.LEFT;
}
else if (keyInfo.Key == ConsoleKey.UpArrow && snake.direction != Direction.DOWN)
{
snake.direction = Direction.UP;
}
else if (keyInfo.Key == ConsoleKey.RightArrow && snake.direction != Direction.LEFT)
{
snake.direction = Direction.RIGHT;
}
else if (keyInfo.Key == ConsoleKey.DownArrow && snake.direction != Direction.UP)
{
snake.direction = Direction.DOWN;
}
}
snake.Update();
if(snake.IsDead())
{
break;
}
if(snake.x == food.x && snake.y == food.y)
{
snake.AddTail(food);
food = foodCreator.CreateFood(snake.points);
}
food.Draw();
snake.Draw();
Thread.Sleep(100);
}
}
}
public class FoodCreator
{
int maxX;
int maxY;
char sym;
public FoodCreator(int maxX, int maxY, char sym)
{
this.maxX = maxX;
this.maxY = maxY;
this.sym = sym;
}
public Point CreateFood(List<Point> exceptionPoints = null)
{
Random rnd = new Random();
Point result = new Point(rnd.Next(0, maxX), rnd.Next(0, maxY), sym);
if (exceptionPoints != null)
{
for (int i = 0; i < exceptionPoints.Count; i++)
{
if (result.x == exceptionPoints[i].x && result.y == exceptionPoints[i].y)
{
i = 0;
result = new Point(rnd.Next(0, maxX), rnd.Next(0, maxY), sym);
}
}
}
return result;
}
}
public class Snake
{
public List points = new List();
public Direction direction { get; set; }
const char sym = '*';
public int x
{
get
{
if (points.Count <= 0)
return 0;
return points[0].x;
}
set
{
if(0 < points.Count)
{
points[0].x = value;
}
}
}
public int y
{
get
{
if (points.Count <= 0)
return 0;
return points[0].y;
}
set
{
if (0 < points.Count)
{
points[0].y = value;
}
}
}
public Snake(Point point, int legnth, Direction direction)
{
points.Add(point);
for(int i = 1; i<legnth; i++)
{
points.Add(new Point(point.x, point.y + i, sym));
}
this.direction = direction;
}
public void Draw()
{
points[points.Count - 1].Clear();
points[0].Draw();
}
public void Update()
{
UpdatePosition();
}
void UpdatePosition()
{
for(int i = points.Count - 1; i > 0; i--)
{
points[i].x = points[i - 1].x;
points[i].y = points[i - 1].y;
}
switch (direction)
{
case Direction.LEFT:
points[0].x -= 1;
break;
case Direction.UP:
points[0].y -= 1;
break;
case Direction.RIGHT:
points[0].x += 1;
break;
case Direction.DOWN:
points[0].y += 1;
break;
}
}
public bool IsDead()
{
if (x < 0 || y < 0 || x>80 || y>20)
{
return true;
}
else if(5 <= points.Count)
{
for(int i=4; i<points.Count; i++)
{
if(x == points[i].x && y == points[i].y)
{
return true;
}
}
}
return false;
}
public void AddTail(Point point)
{
point.sym = sym;
points.Add(point);
}
}
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()
{
Clear();
Console.SetCursorPosition(x, y);
Console.Write(sym);
}
// 점을 지우는 메서드
public void Clear()
{
Console.SetCursorPosition(x, y);
Console.Write(' ');
}
// 두 점이 같은지 비교하는 메서드
public bool IsHit(Point p)
{
return p.x == x && p.y == y;
}
}
// 방향을 표현하는 열거형입니다.
public enum Direction
{
LEFT,
RIGHT,
UP,
DOWN
}
블랙잭 코드
using System; using System.Collections.Generic; using System.Threading;
public enum Suit { Hearts, Diamonds, Clubs, Spades }
public enum Rank { Two = 2, Three, Four, Five, Six, Seven, Eight, Nine, Ten, Jack, Queen, King, Ace }
// 카드 한 장을 표현하는 클래스
public class Card
{
public Suit Suit { get; private set; }
public Rank Rank { get; private set; }
public Card(Suit s, Rank r)
{
Suit = s;
Rank = r;
}
public int GetValue()
{
if ((int)Rank <= 10)
{
return (int)Rank;
}
else if ((int)Rank <= 13)
{
return 10;
}
else
{
return 11;
}
}
public override string ToString()
{
return $"{Rank} of {Suit}";
}
}
// 덱을 표현하는 클래스
public class Deck
{
private List cards;
public Deck()
{
cards = new List();
foreach (Suit s in Enum.GetValues(typeof(Suit)))
{
foreach (Rank r in Enum.GetValues(typeof(Rank)))
{
cards.Add(new Card(s, r));
}
}
Shuffle();
}
public void Shuffle()
{
Random rand = new Random();
for (int i = 0; i < cards.Count; i++)
{
int j = rand.Next(i, cards.Count);
Card temp = cards[i];
cards[i] = cards[j];
cards[j] = temp;
}
}
public Card DrawCard()
{
Card card = cards\[0\];
cards.RemoveAt(0);
return card;
}
}
// 패를 표현하는 클래스
public class Hand
{
private List cards;
public Hand()
{
cards = new List();
}
public void AddCard(Card card)
{
cards.Add(card);
}
public int GetTotalValue()
{
int total = 0;
int aceCount = 0;
foreach (Card card in cards)
{
if (card.Rank == Rank.Ace)
{
aceCount++;
}
total += card.GetValue();
}
while (total > 21 && aceCount > 0)
{
total -= 10;
aceCount--;
}
return total;
}
public override string ToString()
{
if (cards.Count <= 0)
{
return "";
}
string result = cards[0].ToString();
for (int i = 1; i < cards.Count; i++)
{
result += ", " + cards[i].ToString();
}
return result;
}
}
// 플레이어를 표현하는 클래스
public class Player
{
public Hand Hand { get; private set; }
public Player()
{
Hand = new Hand();
}
public Card DrawCardFromDeck(Deck deck)
{
Card drawnCard = deck.DrawCard();
Hand.AddCard(drawnCard);
return drawnCard;
}
}
// 여기부터는 학습자가 작성
// 딜러 클래스를 작성하고, 딜러의 행동 로직을 구현하세요.
public class Dealer : Player
{
// 코드를 여기에 작성하세요
}
// 블랙잭 게임을 구현하세요.
public class Blackjack
{
Player player;
Dealer dealer;
Deck deck;
public Blackjack()
{
player = new Player();
dealer = new Dealer();
deck = new Deck();
}
void PrintGameState()
{
Console.WriteLine($"Dealer's score is {dealer.Hand.GetTotalValue()}");
Console.WriteLine($"Dealer's Hand : {dealer.Hand.ToString()}");
Console.WriteLine("===================================================================");
Console.WriteLine($"Your score is {player.Hand.GetTotalValue()}");
Console.WriteLine($"Your Hand : {player.Hand.ToString()}\\n");
}
public void start()
{
Console.WriteLine("Blackjack!");
Thread.Sleep(1000);
Console.WriteLine($"You get {player.DrawCardFromDeck(deck).ToString()}");
Thread.Sleep(500);
Console.WriteLine($"Dealer gets {dealer.DrawCardFromDeck(deck).ToString()}");
Thread.Sleep(500);
Console.WriteLine($"You get {player.DrawCardFromDeck(deck).ToString()}");
Thread.Sleep(500);
Console.WriteLine($"Dealer gets {dealer.DrawCardFromDeck(deck).ToString()}");
Thread.Sleep(1000);
while (true)
{
Console.Clear();
PrintGameState();
if(21 < player.Hand.GetTotalValue())
{
Console.WriteLine("BUST!!!!!!");
return;
}
Console.WriteLine($"Do you want to draw from deck? (Y/N)");
ConsoleKeyInfo keyInfo = Console.ReadKey(true);
if(keyInfo.Key == ConsoleKey.Y)
{
Console.WriteLine($"You get {player.DrawCardFromDeck(deck).ToString()}");
Thread.Sleep(1500);
continue;
}
else if(keyInfo.Key == ConsoleKey.N)
{
while(true)
{
if (17 <= dealer.Hand.GetTotalValue())
{
Console.WriteLine("Dealer has been finished drawing from deck.");
break;
}
Console.Clear();
PrintGameState();
Thread.Sleep(300);
Console.WriteLine($"Dealer gets {dealer.DrawCardFromDeck(deck).ToString()}");
Thread.Sleep(700);
}
break;
}
else
{
continue;
}
}
Console.Clear();
PrintGameState();
int playerValue = player.Hand.GetTotalValue();
int dealerValue = dealer.Hand.GetTotalValue();
if (playerValue == dealerValue)
{
Console.WriteLine("Draw");
}
else if (playerValue < dealerValue)
{
Console.WriteLine("Lose....");
}
else
{
Console.WriteLine("Win!");
}
}
}
class Class1
{
static void Main(string\[\] args)
{
// 블랙잭 게임을 실행하세요
Blackjack blackjack = new Blackjack();
blackjack.start();
}
}