[My toy] 블랙 잭 - Java

MandarinePunch·2022년 4월 18일
0

토이 프로젝트

목록 보기
5/6
post-thumbnail

✍ 주제 선정

발표를 마친지는 일주일 정도 지났지만! 첫 번째 개인 프로젝트를 기록해보려 한다.
주제는 자유이며, '배운 내용을 토대로 원하는 작품을 만들어봐라!' 라고 지시를 받았다.
주제를 받자마자 든 생각은, '재밌게 구현할 수 있는게 뭐가 있을까?' 하는 생각이었다.
그리고 예전 미니 게임에서 블랙잭을 즐겁게 했던 기억이 떠올라, 블랙잭을 직접 만들어보기로 했다.


📝 게임 규칙

구현을 위해 우선, 게임 규칙을 알아야 할 필요가 있었다.
규칙을 찾아보니... 생각보다 복잡했다.

처음 받은 두 장의 카드가 같은 숫자면 스플릿(각각의 카드로 취급하여 분할하여 게임 진행 가능)을 할 수 있다느니, A는 자신이 원할 때, 1 또는 11로 바꿔 쓸 수 있다느니 등등..
모두 구현하기에는 시간이 빠듯할 거 같았다.

그래도 내가 직접 플레이하면서 느낀 블랙잭의 재미는 없애고 싶지 않아,
재미는 최대한 유지한 상태로 규칙을 조금 수정하였다.
수정한 규칙은 발표 때 사용했던 ppt를 통해 설명해보겠다.

  • 게임이 시작되면 Deck(카드 뭉치)이 만들어지고, 딜러와 플레이어에게 각각 2장의 카드가 지급된다.

  • Deck에 52장의 카드를 모두 넣어줘야 하는데,
    뭔가.. 구현할 건 2인 정원 게임인데 카드 카운팅 의미도 없을 것 같고 해서 Deck 매수를 (1~10)의 카드 30장으로 지정해 줬다.

  • 만약 이 두 장의 카드가 A 또는 10일 경우 BlackJack이 되어 승리하게 되는데,
    이 규칙은 딜러와 플레이어 모두에게 적용되며,
    낮은 확률로 둘 다 BlackJack이 나왔을 경우는 무승부가 된다.

  • BlackJack으로 승리했을 경우, 배팅 금액의 1.5배를 받으며,
    그 외의 방법으로 승리 시 배팅 금액의 2배를 받는다.

  • 본 게임에서는 A를 숫자 1로 고정해주었다.

  • BlackJack이 아닐 경우, 플레이어는 Hit(카드를 받는다), Stay(그만 받는다) 중 하나를 선택할 수 있다.

  • Stay를 선택할 경우 그 판에서는 더이상 카드를 받을 수 없으므로, 게임이 종료된다.

  • Hit를 선택할 시, 카드를 한 장 더 받게되고
    이 때, 카드의 합이 21이 초과했을 경우 Bust로 딜러의 카드 합 및 결과와 관계없이 패배한다.

  • 21이하인 경우는, 본인 판단하에 Stay를 선택해 게임을 종료하여
    딜러의 카드 합과 플레이어의 카드 합 중 높은 쪽이 승리하게 되는 게임이다.

  • 단, 딜러는 한 가지 제약 사항이 있는데, 카드의 합이 17이상일 경우, 카드를 더 이상 얻지 못한다는 규칙이다. 다르게 말하면, 딜러는 무조건 17이상이 될 때까지 카드를 뽑아야만 한다는 의미이다. (Bust가 날 확률이 높아진다)

  • 찾아본 결과, 블랙잭은 플레이어에게 조금 유리하도록 만들어진(현실은 다를수도..?) 게임이라 딜러에게 이러한 제약 사항을 둔 것이다.


🔨 구현

이제 규칙을 알았으니, 구현을 해야한다.
공책에 전체적인 알고리즘을 메모해가며, 큰 틀에서 작은 틀로 구현해나갔다.
아래 ppt는 전체 알고리즘 중 user(Player)의 알고리즘을 간추려 그려놓은 것이다.

public class BlackJack {
	// 카드 배분 및 Hit시 랜덤으로 덱에서 빼기 위해 Random을 선언
	private Random random = new Random();
	private Scanner scan = new Scanner(System.in); 
	private List<Integer> deck = new LinkedList<>();			// 카드 덱
	private List<Integer> userCntCard = new LinkedList<>();		// user 보유 카드
	private List<Integer> dealerCntCard = new LinkedList<>();	// dealer 보유 카드
	private int userSum = 0;	// user 카드 합
	private int dealerSum = 0;	// dealer 카드 합
	private int money = 0;		// 배팅 금액
	private final int BLACK_JACK_NUM = 21;
	private final int DEALER_MIN_NUM = 17;
}

변수들을 위와 같이 선언하였다.
Deck List를 LinkedList로 만든 이유는 랜덤하게 카드를 덱에서 제거해야 하는데,
이럴 경우는 LinkedList가 ArrayList보다 더 효율적이라 판단하여 이렇게 구현하였다.
이렇게 적은 수에서는 거의 차이가 없겠지만 어쨋든...
이 둘의 차이는 나중에 포스팅 해보려고한다! 😁

간단히 말하자면, list 중간에 값이 더해지거나 없어질 경우,

  • LinkedList는 해당 index의 앞 뒤 index를 이용해 list에 값을 더하거나 제거하지만,
  • ArrayList는 해당 index로부터 뒷 부분 전체를
    추가하거나 제거할 값의 개수만큼 이동시켜 list에 값을 추가 및 제거한다.

이제 본론으로 돌아와서,
모든 scan 입력 값은 아래 코드와 같이 예외처리를 해줬다.

public void playGame() {
	...
    while(true) {
		System.out.println("1.게임 시작  2.게임 종료");
		selectNum = selectInput();
    ...
}

private int selectInput() {
	int selectNum;
	while (true) {
		try {
			selectNum = Integer.parseInt(scan.nextLine());
			break;
		} catch (NumberFormatException e) {
			System.out.println("에러 원인 = " + e.getMessage());
			System.out.println("입력은 숫자로 하셔야 합니다! 번호를 다시 입력해 주세요.");
			continue;
		} catch (Exception e) {
			System.out.println("알 수 없는 입력이에요. 정확한 번호를 입력해 주세요.");
			continue;
		}
	}
	return selectNum;
}

- 덱 만들기

게임이 시작됐으니, Deck을 만들어줘야 한다.

// 카드 덱 만들기
private void makeDeck() {
	for (int i = 0; i < 30; i++) {
		deck.add(i % 10 + 1);
	}

//	블랙잭 확인용
//	for(int i=0;i<2;i++) {
//		deck.add(1);
//		deck.add(10);
//		deck.add(1);
//	}

}

나머지 연산을 이용해 1~10 까지의 카드를 덱에 넣어줬다.
아래의 for문은 BlackJack으로 결과가 나왔을 때, 이상이 있는지 확인하기 위해 만들어 줬다.

- 카드 두 장 배분

private void drawTwoCard() {
	for (int i = 0; i < 2; i++) {
		int rand1 = deck.remove(random.nextInt(deck.size()));
		int rand2 = deck.remove(random.nextInt(deck.size()));
		userSum += rand1;
		dealerSum += rand2;
		userCntCard.add(rand1);
		dealerCntCard.add(rand2);
	}
}

덱이 만들어지면, 딜러와 플레이어에게 2장씩 카드를 지급한다.

...
drawTwoCard();

// 블랙잭일 경우
if ((userCntCard.contains(1) && userCntCard.contains(10)) 
	|| (dealerCntCard.contains(1) && dealerCntCard.contains(10))) {
		printResult.checkBlackJack(userSum, dealerSum, userCntCard, dealerCntCard, money);
		break;
}
// 아닐 경우
printResult.currentCheck(userSum, dealerSum, userCntCard, dealerCntCard);
...

이 때, 블랙잭일 경우는 결과를 출력하며 게임이 종료되고,
아닐 경우는 Hit or Stay부분으로 넘어간다.

- Hit 메서드

private void hit() {
	// 덱에서 랜덤하게 카드를 제거
	int rand1 = deck.remove(random.nextInt(deck.size()));
    // 그 값을 보유 카드에 추가
	userCntCard.add(rand1);
	userSum += rand1;
    
    // 딜러도 같이 hit를 하게 되는데 카드 합이 17미만일 경우만 hit를 한다.
	if (dealerSum < DEALER_MIN_NUM) {
		int rand2 = deck.remove(random.nextInt(deck.size()));
		dealerSum += rand2;
		dealerCntCard.add(rand2);
	}
}

// hit로 게임이 끝났을 경우 결과 출력 메서드 (딜러 혹은 자신이 Bust가 난 경우)
private void hitResult(PrintString printResult) {
	if (userSum > BLACK_JACK_NUM) {
		printResult.userLose(userSum, dealerSum, userCntCard, dealerCntCard);
	} else if (dealerSum > BLACK_JACK_NUM) {
		printResult.userWin(userSum, dealerSum, userCntCard, dealerCntCard, money);
	}
}

- Stay 메서드

private void stay() {
	// 플레이어가 stay시 딜러의 카드가 17미만일 경우 그 이상이 될 때까지 카드를 뽑는다.
	while (dealerSum < DEALER_MIN_NUM) {
		int randCard = deck.remove(random.nextInt(deck.size()));
		dealerCntCard.add(randCard);
		dealerSum += randCard;
	}
}

// stay시 결과 출력 메서드
private void stayResult(PrintString printResult) {
	if (dealerSum > BLACK_JACK_NUM) {
		printResult.userWin(userSum, dealerSum, userCntCard, dealerCntCard, money);
	} else if (userSum > dealerSum) {
		printResult.userWin(userSum, dealerSum, userCntCard, dealerCntCard, money);
	} else if (userSum == dealerSum) {
		printResult.userDraw(userSum, dealerSum, userCntCard, dealerCntCard, money);
	} else if (userSum < dealerSum) {
		printResult.userLose(userSum, dealerSum, userCntCard, dealerCntCard);
	}
}

🎉 프로그램 시연

승리

패배

블랙 잭 결과

승리

패배

무승부

처음에는 출력 콘솔창을 좀 예쁘게 하고 싶어서 출력문을 이것 저것 만져보았다.
하지만, Simple is best라고 ㅋㅋ 카드 카운팅이나 다른 조건 확인에 있어 그냥 텍스트만 보여주는게 깔끔한 것 같아 출력 문구 라인만 조금 맞춰줬다.

나름 재밌게 만들어져서 뿌듯하다.😆

블랙 잭 코드 - GitHub

profile
개발을 좋아하는 귤나라 사람입니다.

0개의 댓글