본문 바로가기
JAVA

블랙잭 게임 코드 피드백 #1

by mingutistory 2020. 3. 25.
728x90

OKKY fender님의 칼럼 지적대로 지금의 나는 객체지향적 사고를 전혀 하지 못하고 있다는 인상을 계속 받고 있다. 아무래도 책을 다시 한번 읽는 것이 필요해보인다. 코드 공유하면서도 생각했지만 누구나 코드를 짤 수는 있다. 효율성과 재사용성의 문제인듯하다. 

부끄럽지만 기록을 위해 깃허브에 코드를 올렸다. 꼴보기 싫어서안 올리려고 했는데 나는 기록의 힘을 믿는 사람이기 때문에 계속 작성한다.

 

도메인 : 사용자들이 관심을 가지는 특정 분야, 주제. 소프트웨어는 도메인에 존재하는 문제를 해결하기 위해서 개발.

객체지향 모델링 : 실제 세계의 특정 도메인을 코드로 옮겨서 표현하는 과정. 

 

내가 정의 했던 규칙

1. 참여자는 딜러, 게이머 2명만 존재

2. 카드는 총 52장

 - 무늬(다이아, 클로버, 하트, 스페이스)별로 13장

 - 2~10까지는 그대로 점수 반영

 - J, Q, K는 10점. A는 1점

3. 게임 시작시 참여자는 모두 2장을 받고 시작함

 - 게이머는 추가로 카드를 항상 뽑을 수 있음

 - 딜러는 16점 이하인 경우 반드시 1장 뽑고, 17점 이하인 경우 뽑지 않음

4. 딜러, 게이머의 점수와 비교해서 승부

    - 딜러의 카드 1장은 공개하지 않음

 - 카드 오픈 시 카드의 합이 21에 가까운 쪽이 승리

 

규칙에서 추출해본 주요 객체

나 : 딜러, 게이머, 카드, 규칙

다른분들 : 딜러, 게이머, 카드덱, 카드, 규칙 

-

카드덱 객체가 필요하다고 판단 > 클래스를 생성하였으나 클래스를 사용하지 않았다. 딜러가 카드덱을 생성한다는 동작으로 생각했다. 이렇게 되는 경우에 카드덱은 그 객체만으로 이용 할 수 없었고 계속 딜러를 통해서 이용을 해야했다. 비효율적. 

 

규칙 같은 경우에도 처음에는 나는 딜러의 동작에 > 승패 판단, 점수 측정이 다 들어간다고 생각해서 딜러의 동작에 모두 때려넣으려고 했으나 그러면 중복되는 행동이 생기는 것 같아서 따로 규칙 객체를 생성하였다. 

 

여러 글을 읽은 뒤에 내린 결론은 내가 객체를 추출한 것이 아니라 클래스 별로 구별을 했다는 생각이 든다. 이게 아직은 확 와닿는 설명은 아니지만 (내 스스로에게). 그리고 실행 클래스에서 너무 많은 동작을 하고 있어서 내가 제대로 객체 지향을 이해 못하고 있구나 느꼈다.  

 

카드

- 무늬, 숫자를 가짐

딜러

- 카드덱을 생성함

- 랜덤으로 카드를 1장 나눠줌

- 합이 17미만인 경우 이상으로 만듬

게이머

- hit를 선택하면 카드를 한장 더 받음

규칙

- 플레이어가 가지고 있는 카드를 출력함

- 플레이어가 가지고 있는 카드의 합을 출력함

- 버스트(총 합이 21이 넘은 경우) 상황인지 확인함

- 누가 이겼는지 확인함

실행 (클래스)

- 전체 카드덱을 생성함

- 딜러 카드덱과 게이머 카드덱을 생성함

- 딜러 카드덱과 게이머 카드덱의 합을 구함

- 게이머가 버스트가 되지 않을 때까지 hit or stand 선택 반복 

  > hit를 선택한 경우

  - 게이머가 hit 동작하게 함

  - 게이머 카드덱에 한장을 더 추가함

  - 규칙에서 카드를 출력하게 함

  - 게이머가 버스트 상황인지 확인 함

  > stand를 선택한 경우

  - 딜러가 합이 17 미만인 경우 이상으로 만들게 동작하게 함

  - 딜러 카드덱의 합을 구함

  - 딜러가 버스트 상황인지 확인 함 > 버스트 상황이면 딜러 패배 / 아니라면 진행

  - 규칙에서 누가 승리했는지 판단하게 함

 

다른 분들의 글을 참고하면서 다시 주요 객체들의 동작을 정리 해보기로 한다.

 

카드

- 무늬, 숫자가 존재함

  > 무늬 : 스페이드, 크로버, 다이아몬드, 하트

  > 숫자 : A(1), 2, 3, 4, 5, 6, 7, 8, 9, J(10), Q(10), K(10)

 

카드덱

- 52개의 카드로 이루어져 있음

- 딜러, 게이머에게 카드를 제공함

 

딜러

- 카드덱에서 카드를 받음

- 카드 총합이 16 이하면 17 이상이 될 때 까지 카드를 더 받음

- 카드를 오픈 함

 

게이머

- 카드덱에서 카드를 받음

- 카드를 오픈 함

 

규칙

- 딜러와 게이머의 점수를 계산한다.

- 승패를 판단한다. 

 

하단의 멋진 블로거분들의 자료를 참고해서 요리조리 바꿔봤다. 

변경 내용

1. BlackJack.java -> Main.java로 변경

지금은 BlackJack 클래스가 실행 클래스로 일을 했는데 실행 클래스는 따로 뺌.

Main 클래스에서 게임을 진행 시킬 BlackJack 클래스의 인스턴스를 생성하고 BlackJack 클래스 안의 play 메소드를 호출해 게임을 실행시킴. 이 BlackJack 클래스에서 게임에 필요한 클래스들의 인스턴스를 생성시킴. 

 

2. Card 클래스 enum 사용

왜 이렇게 사용해야 하는지 조차도 지금 이해 못하고 있음.

이건 꼭 해줄거긴 한데 나중으로 미룬다.

 

3. CardDeck.java 클래스의 생성자로 카드덱 생성 위치 변경 

카드덱의 동작 : 딜러, 게이머에게 카드를 제공함을 실행 시키기 위해서는 52개의 카드로 이루어진 카드덱이 존재해야 함으로 생성자를 사용해서 초기화를 해준다. 

 

public class CardDeck {
	
	private static ArrayList<Card> deck = new ArrayList<Card>(); 
	private static String[] PATTERNS = {"hearts", "spades", "diamonds", "clubs"};
	private static int CARD_CNT = 13; 
	

	public CardDeck() {
		
		for(String pattern : PATTERNS) {
			for(int i = 1; i <= CARD_CNT; i++) {				
				Card card = new Card(); 
				
				switch(i) {
				case 1 :
					card.setNumber("A");
					break;
				case 11 :
					card.setNumber("J");
					break;
				case 12 :
					card.setNumber("Q");
					break;
				case 13 :
					card.setNumber("K");
					break;
				default :
					card.setNumber(Integer.toString(i));
					break;
				}
				
				card.setPattern(pattern);
				deck.add(card); 			
			}
		}
		

	}
   
}

그렇게 해서 탄생했던 코드가 이 것.  하지만 생성자에는 호출 및 초기화의 기능만 있으면 되지 저렇게 카드덱을 생성하는 과정이 오지 않아도 된다는 이야기 !

그래서 코드 분리가 필요함. 참고하고 있는 블로거님은 한번 더 코드를 나누셨는데 나는 그럴 필요가 딱히 없는 듯 함 + switch문을 사용해서 분리하기 더 힘듬의 이유로 그냥 묶어서 분리했다. 

 

public class CardDeck {
	
	private static List<Card> deck; 
	private static String[] PATTERNS = {"hearts", "spades", "diamonds", "clubs"};
	private static int CARD_CNT = 13; 
	

	public CardDeck() {
		deck = this.generatedDeck();
	}
	
	private ArrayList<Card> generatedDeck() {
		ArrayList<Card> deck = new ArrayList<Card>();
		
		for(String pattern : PATTERNS) {
			for(int i = 1; i <= CARD_CNT; i++) {				
				Card card = new Card(); 
				
				switch(i) {
				case 1 :
					card.setNumber("A");
					break;
				case 11 :
					card.setNumber("J");
					break;
				case 12 :
					card.setNumber("Q");
					break;
				case 13 :
					card.setNumber("K");
					break;
				default :
					card.setNumber(Integer.toString(i));
					break;
				}
				
				card.setPattern(pattern);
				deck.add(card); 			
			}
		}
		
		return deck; 
	}
   
}

 

확실히 이렇게 사용하는 것이 옳은 듯 하다. 

이전 코드에서는 딜러 클래스에서 카드덱을 생성해서 이 곳 저곳으로 넘기느라고 모두 딜러 쪽으로 메소드를 쏟아 넣듯이 했었는데 그럴 필요가 없어보인다.

 

Card 객체의 생성자를 기본 생성자가 아닌 매개변수가 있는 생성자로 생성하는 것을 보았다. Card 객체의 문자, 숫자 이렇게 짝을 이루는 것을 강제화 시키기 위함이다. 

 

public class CardDeck {
	
	private static List<Card> deck; 
	private static String[] PATTERNS = {"hearts", "spades", "diamonds", "clubs"};
	private static int CARD_CNT = 13; 
	

	public CardDeck() {
		deck = this.generatedDeck();
	}
	
	private ArrayList<Card> generatedDeck() {
		ArrayList<Card> deck = new ArrayList<Card>();
		
		for(String pattern : PATTERNS) {
			for(int i = 1; i <= CARD_CNT; i++) {
				
				String number = ""; 
				
				switch(i) {
				case 1 :
					number = "A"; 
					break;
				case 11 :
					number = "J";
					break;
				case 12 :
					number = "Q";
					break;
				case 13 :
					number = "K";
					break;
				default :
					number = Integer.toString(i);
					break;
				}
				
				Card card = new Card(pattern, number); 
				deck.add(card); 			
			}
		}
		
		return deck; 
	}
   
}

이런 식으로 변경해줬다.  

변수명도 혼란스러울 수 있을 것 같아서 (String형인데 변수명이 number임)  denomination로 변경했다. 이제와서 변수 바꾸려니까 진짜 귀찮았다. 처음부터 잘 정하도록 하자.

 

 

4. 규칙에서 출력하는 부분을 toString 메소드 안으로 넣어준다. 

부끄러운 부분이다. 카드 출력하는 부분을 toString 메소드를 오라이드해서 만들어준다. 

계속해서 for문으로 출력하고 있었던 입장으로서 눈물이 앞을 가린다. 

 

CardDeck이 제대로 생성되는지 확인하는 부분에서 StringBuilder를 사용하는 것을 보았다. 처음보는 클래스여서 한번 찾아봤다. 찾아보지 않고 쓰던 거 계속 써 버릇하면 똥된다.

http://hardlearner.tistory.com/288

 

 

5. 카드를 나눠주는 동작의 주체를 Dealer.java에서 CardDeck.java로 변경한다.

	// 카드 나눠 주기 
	public Card getCard(ArrayList<Card> deck) {
		
		int size = deck.size();
		int num = (int)(Math.random() * size);
		Card card = deck.get(num);
		deck.remove(num);
		
		return card;
	}

원래는 dealer 클래스에 있던 메소드다. 

처음 카드덱이 유지되어야 하니까 딜러 클래스에서 카드덱을 생성하고 > 그 카드덱을 받아서 그 안에서 카드를 뽑는다 라는 과정으로 생각해서 이런식으로 짰다.

 

하지만 카드덱 객체를 이용하니 더 깔끔하게 코드 작성 할 수 있다.

 

6. 랜덤 정렬, ArrayList로 구현한 카드덱을 Stack으로 변경

 

하지만 저렇게 매번 카드를 뽑을 때 마다 random() 함수를 사용해서 하는 것은 비효율적이다. 그래서 애초에 처음부터 배열을 랜덤 정렬 한 뒤에 사용하면 편리하다. 카드덱을 생성하고 바로 랜덤 정렬을 해준다. 

이 때 사용하는 메소드가 Collections.shuffle()

그리고 ArrayList.get(0); 이렇게 해서 뽑아서 사용하는 것 보다는 Stack.pop()을 이용해서 뽑아서 사용하는 것이 더욱 직관적이므로 그렇게 변경해준다. 

 

그래서 결론은 이런 코드가 되었다.

 

public class CardDeck {
	
	private static Stack<Card> deck; 
	private static String[] PATTERNS = {"hearts", "spades", "diamonds", "clubs"};
	private static int CARD_CNT = 13; 
	

	public CardDeck() {
		deck = this.generatedDeck();
		Collections.shuffle(deck);
	}
	
	private Stack<Card> generatedDeck() {
		Stack<Card> deck = new Stack<Card>();
		
		for(String pattern : PATTERNS) {
			for(int i = 1; i <= CARD_CNT; i++) {
				
				String number = ""; 
				
				switch(i) {
				case 1 :
					number = "A"; 
					break;
				case 11 :
					number = "J";
					break;
				case 12 :
					number = "Q";
					break;
				case 13 :
					number = "K";
					break;
				default :
					number = Integer.toString(i);
					break;
				}
				
				Card card = new Card(pattern, number); 
				deck.add(card); 			
			}
		}
		
		return deck; 
	}
	
	// 카드 나누기
	public Card splitCard() {
		return deck.pop();
	}
	
	@Override 
	public String toString() {
        StringBuilder sb = new StringBuilder();

        for(Card card : deck){
            sb.append(card.toString());
            sb.append("\n");
        }

        return sb.toString();		
	}
	
    public Stack<Card> getDeck() {
        return deck;
    }

   
}

 

 

-

너무 고칠 부분이 많아서 몇 일 나눠서 해야겠다. 몇 시간이 걸리는거지 (혼란)

진짜 너무 부족한 부분이 많다는 걸 한 번 더 느끼는 중 ..

더 명확하게 공부 해야 하는 부분 : static, 생성자, enum, 오버라이딩, 오버로딩

 

깃허브 주소

https://github.com/minchoi9509/blackjack

 

minchoi9509/blackjack

블랙잭 게임 수정 중. Contribute to minchoi9509/blackjack development by creating an account on GitHub.

github.com

 

참고하는 글

https://jojoldu.tistory.com/62

 

객체지향 좀 더 이해하기 - 블랙잭 게임 구현 (1)

순수 Java로 이루어진 프로젝트 객체지향을 이해하는데 있어 게시판은 좋은 예제가 아니라는 자바지기(박재성)님과 OKKY fender님의 이야기로 시작한 프로젝트 Java로 웹을 한다고 하면서 실제로 Java와 객체지향..

jojoldu.tistory.com

https://blog.naver.com/bell2017/221240385067

 

블랙잭2 요구사항 분석 및 객체 모델링

객체지향 모델링 실제새계의 특정 도메인(개념이나 동작 등을) 코드라는 매체로 옮겨서 표현하는 과정 객체...

blog.naver.com

https://woowabros.github.io/study/2016/07/07/think_object_oriented.html

 

생각하라, 객체지향처럼 - 우아한형제들 기술 블로그

2년차 쪼랩이가 객체지향을 처음으로 접하고 공부를 하면서 나름대로 정리해보았습니다.

woowabros.github.io

 https://okky.kr/article/358197

 

OKKY | 초보 개발자에게 권장하는 객체지향 모델링 공부 방법

원래 다른 글타래 의 답글로 적던 내용인데 다른 분들에게도 혹시 도움이 될까 싶어서 별도 글타래로 옮깁니다. 따라서 다소 맥락이 어색한 부분이 있는 점 양해부탁 드립니다. 아마도 객체지향 모델링 이라는 표현을 자주 들어보셨을 것입니다. 여기서 이야기하는 모델링(modelling) 은 흔히 말하는 3D 모델링 이나 프라모델 같은 단어에서

okky.kr

 

300x250

댓글