기능하는 블랙잭 코드이다. 출력하는 면에 있어서 약간의 아쉬운점이 있지만, 본래대로 동작한다.
블랙잭 규칙
기본 규칙은 가진 카드의 합이 21을 초과하지 않는 가장 큰 수를 가진 사람이 이긴다. 시작은 카드 2장을 받고 시작한다.
플레이어의 경우 선택하여 Hit 하거나 Stay할 수 있다.
딜러의 경우 17미만이라면 반드시 Hit, 17이상이면 반드시 Stay해야한다.
Hit은 내 카드를 한장을 추가한다. 그리고 다시 Hit할지 Stay할지 선택한다.
Stay는 이제 더이상 카드를 추가하지 않고, 다음 딜러의 차례가 진행된다.
만약 합이 21이 넘으면 Bust가 되서 게임에서 지게 된다.
A카드는 1혹은 11로 사용 가능하고, J,Q,K는 10으로 사용한다.
만약 합의 수가 같다면, 비기지만, 블랙잭이라는것이 있다.
블랙잭은 처음 받은 카드 두장이 21인 경우이다. 플레이어가 블랙잭을 하면 플레이어 승리이고, 그렇지 않고 딜러가 블랙잭을 하면 딜러의 승리이다. 만약 둘다 블랙잭이 아니라면 비긴다.
코드
using Microsoft.VisualBasic;
using System.Runtime.CompilerServices;
namespace ConsoleRPG
{
internal class Program
{
public static class CardPool
{
static int[] CardArr = new int[13];
static String[] CardElements = { "A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K" };
static public int remainingCardNum = 0;
static Random _rng = new Random();
public static void ResetDeck()
{
if (remainingCardNum != 0)
return;
Console.WriteLine("Card Pool is empty, Resetting Cards");
for (int i = 0; i < 13; i++)
{
CardArr[i] = 4;
}
remainingCardNum = 13 * 4;
}
static int CardByIndex(int index)
{
string s = CardElements[index];
int i = 0;
switch (index)
{
case 0:
i = 11;
break;
case 10:
case 11:
case 12:
i = 10;
break;
default:
i = int.Parse(s);
break;
}
return i;
}
public static int PushCard(bool isme = false)
{
if(remainingCardNum == 0)
ResetDeck();
List<int> possible = new List<int>();
for(int i = 0; i < 13; i++)
if(CardArr[i] != 0)
possible.Add(i);
int randomindex = possible[_rng.Next(possible.Count)];
if (isme)
Console.WriteLine($"Pulled out ' {CardElements[randomindex]} ' !");
CardArr[randomindex]--;
remainingCardNum--;
return CardByIndex(randomindex);
}
}
public abstract class Players
{
protected List<int> cards = new List<int>();
protected int sum;
public int cardCount;
protected Queue<int> AceIndex = new Queue<int> ();
public string name;
public int GetSum()
{
return sum;
}
public void Init()
{
sum = 0;
Hit(false);
Hit(false);
IsEnd();
}
private void CalculateSum()
{
sum = 0;
for (int i = 0; i < cards.Count; i++)
{
sum += cards[i];
}
}
public void Hit(bool isName)
{
int c = CardPool.PushCard(isName);
this.cards.Add(c);
if(c == 11)
this.AceIndex.Enqueue(this.cardCount);
this.cardCount++;
}
public bool IsEnd()
{
CalculateSum();
while (this.sum >= 22)
{
if (this.AceIndex.Count > 0)
{
cards[AceIndex.Dequeue()] = 1;
CalculateSum();
}
else
{
Console.WriteLine(name + " Busted! with " + this.sum.ToString());
return true;
}
}
return false;
}
public abstract void PlayGame();
public abstract void ShowCard();
}
public class Me : Players
{
public Me()
{
name = "Player";
this.Init();
}
public override void ShowCard()
{
Console.Write(name + " Cards are : ");
for (int i = 0; i < cardCount; i++)
{
Console.Write(this.cards[i]);
if (i != cardCount - 1)
Console.Write(", ");
else
Console.WriteLine();
}
Console.WriteLine("Player Sum is : " + this.sum.ToString());
}
public override void PlayGame()
{
do
{
ShowCard();
int input = -1;
do
{
Console.WriteLine("Press 1. Hit or 0. Stay : ");
string s = Console.ReadLine();
if (int.TryParse(s, out input) && (input == 1 || input == 0))
break;
} while (true);
if (input == 0)
{
Console.WriteLine($"You stay with sum {this.sum}");
return;
}
Console.WriteLine("You hit!");
this.Hit(true);
Console.WriteLine("=========================");
} while (!IsEnd());
}
}
public class Dealer : Players
{
public bool IsShow = false;
public Dealer()
{
name = "Dealer";
this.Init();
}
public override void ShowCard()
{
if (IsShow)
{
Console.Write("Dealer : ");
for (int i = 0; i < cardCount; i++)
{
Console.Write(this.cards[i]);
if (i != cardCount - 1)
Console.Write(", ");
else
Console.WriteLine(" With Total Sum " + sum.ToString());
}
}
else
{
Console.Write("Dealer : ");
Console.Write(this.cards[0]);
Console.WriteLine(", ?");
}
}
public override void PlayGame()
{
do
{
ShowCard();
if (this.sum >= 17)
{
Console.WriteLine($"Dealer stay with sum {this.sum}");
return;
}
Console.WriteLine("Dealer hit!");
this.Hit(IsShow);
} while (!IsEnd());
}
}
static void Main(string[] args)
{
CardPool.ResetDeck();
Console.WriteLine("Game Start!");
string input = "";
do
{
Me player = new Me();
Dealer dealer = new Dealer();
Console.WriteLine("=========================");
dealer.ShowCard();
Console.WriteLine("=========================");
Console.WriteLine("Player's turn!");
player.PlayGame();
Console.WriteLine("=========================");
dealer.IsShow = true;
Console.WriteLine("Dealer's turn!");
dealer.PlayGame();
if (player.GetSum() > 21)
Console.WriteLine("Player Busted, Player Lose!");
else if (dealer.GetSum() > 21)
Console.WriteLine("Dealer Busted, Player Wins!");
else
{
Console.WriteLine("Player : " + player.GetSum());
Console.WriteLine("Dealer : " + dealer.GetSum());
if (player.GetSum() > dealer.GetSum())
Console.WriteLine(("Player Wins!"));
else if (player.GetSum() == dealer.GetSum())
{
if (player.GetSum() == 21)
{
if (player.cardCount == 2)
Console.WriteLine("Player Wins with BlackJack!");
else if (dealer.cardCount == 2)
{
Console.WriteLine("Dealer Wins with BlackJack!");
Console.WriteLine("Player Lose!");
}
else
Console.WriteLine("It's a tie");
}
else
Console.WriteLine("It's a tie");
}
else
Console.WriteLine("Player Lose!");
}
Console.WriteLine("If you want to Continue, Press C. If you want to Quit, Press any otehr Key : ");
input = Console.ReadLine();
} while (input == "C" || input == "c");
}
}
}
우리는 공통으로 같은 덱을 사용해서 카드를 추출할 것이다. 그래서 CardPool을 매번 생성하지않고, static 클래스로 정의하여 사용하였다.
CardPool내부에는 실제 트럼프 카드 덱 (각 카드 4장씩), 남은 카드, 그리고 Random을 정의하였다.
CardPool에 카드가 다 떨어지면 ResetDeck을 호출하여 카드를 다시 채워준다. 단 Static 클래스이기 때문에, CardPool을 부르기 전 ResetDeck을 한번은 해줘야 한다.
CardByIndex는 카드 인덱스에 따른 카드 점수를 반환한다.
PushCard를 통해 카드를 뽑아준다. 만약 isme가 true라면, 무엇을 뽑았는지도 알려준다.
abstract 클래스이다. 몇몇 재정의할 함수가 있고, 나머지는 동일하기에 abstract클래스로 작성하였다.
몇몇 변수들은 protected인데, private으로 하면 자식에서도 참조가 안되기 때문에 자식은 상속 가능한 protected로 해주었다.
Init에서 sum을 0으로 초기화하고, 카드 두장을 뽑는다. 이때 무슨 카드를 뽑는지 출력 하진 않는다. 그리고 sum을 업데이트 한다. 이때 최초에 A, A를 뽑을 가능성이 있어, CalculateSum이 아닌 IsEnd로 작업 해주어야 한다.
CalculateSum에서는 합을 업데이트 해주기만 한다.
Hit에서는 카드 풀에서 카드 한장을 뽑는다. 만약 A를 뽑았다면, AceIndex에 해당 위치를 넣어준다.
IsEnd는 종료 조건을 확인한다. 만약 합이 22이상이면, 우선 A가 있는지 확인한다, A가 있다면 A를 11 대신 1로 사용해보고, 22미만이 될때까지 반복한다. 그래도 A가 없다면, Bust상황이 발생해서 종료를 위해 true를 반환한다. 아니라면 false를 반환하고 진행을 계속 해 줄것이다.
Abstract 메서드 PlayerGame, ShowCard가 존재한다. 이는 오버라이딩 해줄 것이다.
실제 게임하는 플레이어이다.
생성자는 이름을 "Player"로 설정하고, Init을 해준다.
ShowCard에서는 현재 플레이어가 가지고 있는 카드를 전부 보여준다.
PlayGame에서는 유저의인풋을 받아 Hit할지 Stay할지 결정한다. 인풋을 받아 게임 플레이를 조절하고, IsEnd를 확인하여 Bust가 되기전, 혹은 Stay를 하기 전까지 확인을 해야한다.
딜러 봇이다.
생성자는 이름을 "Dealer"로 설정하고 Init을 해준다.
Dealer은 추가로 isShow라는 변수가 존재한다. 이는 카드를 보여주기 위함이다.
ShowCard에서 딜러는 카드가 한장 가려진채로 공개가 되야한다. 그리고 딜러의 게임이 시작될때 이 카드를 포함해서 공개를 해 주어야 한다.
isShow가 기본으로 false로 설정이 되어있어, false일 경우는 플레이어 턴이고, 딜러의 카드는 반드시 두장이다. 즉 출력 시 첫번째 원소와 "?"를 출력하여 카드를 못보게 하자.
isShow가 true는 Main에서 설정해준다. 이때는 딜러 턴이므로 모든 카드를 공개해도 된다 (플레이어의 턴이 종료되었기 때문). 위의 Me와 같이 출력해준다.
PlayGame은 조건에 따라 플레이를 해야한다. 만약 현재 합이 17이상이라면 반드시 종료하고, 아니라면 계속 Hit을 해야한다.
카드풀을 리셋해주고, 게임을 do-While로 시작한다
나인 Me와 딜러인 Dealer을 만들어주면, 생성자를 통해 각자 카드 두장씩 받을것이다.
우선 딜러의 카드를 보여주자. isShow가 False이므로 뒤카드는 가려져있다.
그리고 플레이어의 턴을 진행한다. 플레이어의 턴이 끝나면 딜러의 isShow를 true로 바꿔주고, 딜러의 턴이 진행된다.
아래 if와 else if, else문은 상황에 따라 결과를 출력해준다. 위에 설명한 방식대로
1. 플레이어가 버스트 한다면 플레이어 패배
2. 1이 아니고 딜러가 버스트 한다면 딜러 패배
3. 2또한 아니면, 숫자가 큰 사람이 이김
4. 3에서 숫자가 같다면, 블랙잭 규칙을 카드수로 파악하고, 승리자를 판별한다.
이후, 계속 하고싶으면 'c'또는 'C'를 입력하고, 그만하고 싶으면 다른 아무거나 누르면 된다.
생각보다 구현이 잘 되어있지만, 실제 카드 문자로 표시되는 대신, 숫자로 저장하여 카드가 아니라 점수로 카드 리스트가 나오는게 아쉽다. (이는 쉽게 해결 가능하긴 하다)