우아한테크코스 프리코스 Week 3. 블랙잭

우아한테크코스 프리코스 기간 동안의 후기와 소감을 적은 연재글입니다.

우테코 프리코스 1기 때와 같은 내용이었던 숫자야구와 레이싱 게임. 그래서 3주차는 당연히 로또 추첨 프로그램인줄 알고 있었는데, 블랙잭 게임 만들기가 떴다.

기능 요구사항

  • 블랙잭 게임을 진행하는 프로그램을 구현한다. 블랙잭 게임은 딜러와 플레이어 중 카드의 합이 21 또는 21에 가장 가까운 숫자를 가지는 쪽이 이기는 게임이다.
  • 플레이어는 게임을 시작할 때 배팅 금액을 정해야 한다. 카드의 숫자 계산은 카드 숫자를 기본으로 하며, 예외로 Ace는 1 또는 11로 계산할 수 있으며, King, Queen, Jack은 각각 10으로 계산한다.
  • 게임을 시작하면 플레이어는 두 장의 카드를 지급 받으며, 두 장의 카드 숫자를 합쳐 21을 초과하지 않으면서 21에 가깝게 만들면 이긴다. 21을 넘지 않을 경우 원한다면 얼마든지 카드를 계속 뽑을 수 있다. 단, 카드를 추가로 뽑아 21을 초과할 경우 배팅 금액을 모두 잃게 된다.
  • 처음 두 장의 카드 합이 21일 경우 블랙잭이 되면 베팅 금액의 1.5 배를 딜러에게 받는다. 딜러와 플레이어가 모두 동시에 블랙잭인 경우 플레이어는 베팅한 금액을 돌려받는다.
  • 딜러는 처음에 받은 2장의 합계가 16이하이면 반드시 1장의 카드를 추가로 받아야 하고, 17점 이상이면 추가로 받을 수 없다. 딜러가 21을 초과하면 그 시점까지 남아 있던 플레이어들은 가지고 있는 패에 상관 없이 승리해 베팅 금액을 받는다.

우선 블랙잭 룰도 모르고, 미션과 함께 적힌 설명은 모호한 부분이 많아서 블랙잭 룰에 대해서 구글을 열심히 뒤져보는 데에만 두세 시간이 걸렸던 것 같다.

  1. 왜 딜러 카드는 한 장만 표시되는가? -> 한 장은 뒤집어 놓았다.
  2. 점수 계산은 어떻게? -> 플레이어 개개인과 딜러 간에만 계산한다.
  3. 딜러의 추가 카드는 한 장만? -> 17 이상이 될 때까지 몇 장이라도
  4. 카드가 다 떨어지면 어떡하지? -> 블랙잭 룰 상 플레이어는 8명까지만
  5. 베팅 금액을 잃는다 -> 버스트, 카드를 한장 더 뽑는다 -> 힛 등의 용어들.

실행 예시의 배당금 숫자가 틀려서 나중에 재공지되는 등, 혼란 속에서 코드 짜기가 시작되었다…

잘못 알고 있던 것

자바의 객체 지향에 대해서, 마치 현실 세계를 투영해서 현실의 자동차가 하는 일을 자동차 클래스가 해야 하고, 현실의 심판이 하는 일을 컨트롤러가 해야 하고 그런 줄 알았다. 이렇게 오해하고 과제 제출할 때 쓴 메일에도 당당히 써 놓고 했었는데… 정보 은닉과 TDA(Tell, Don’t Ask), MVC 패턴에 대한 글들을 읽어보고 생각의 방향을 바꾸게 되었다.

시작부터 난관, 그리고 MVC 패턴

모델-뷰-컨트롤러 디자인 패턴에 따라 코드를 처음부터 분리해서 짜 보았다. 이번 미션은 처음부터 모델 코드의 일부분이 완성되어 있었기에 방향을 잡기가 수월했다. 코딩하는 내내 갑자기 훅 늘어난 난이도에 고통받긴 했지만.

그도 그럴 것이 이번 주차의 제한 조건이…

  1. 건드릴 수 없는 생성자와 접근 제어자.
  2. 삼항 연산자 금지, else 금지, 들여쓰기 1까지만 허용, 함수 10라인까지만 허용

처음에는 MVC의 개념을 아무리 읽어도 이해가 되지 않았다. 그래서 파일만 나눠놓고 모델에서 뷰로 바로 인자를 전달하거나, 컨트롤러에서 바로 출력하거나… 개판이었는데, 어느 순간에 깨달음이 왔다. 아 모델이 뷰나 컨트롤러를 몰라야 한다는 게 이 소리였구나, 뭐 이런 거.
그렇게 짜놨던 코드를 MVC 패턴에 맞게 전면적으로 리팩토링했다.

너무도 편한 문자열 처리

Java8 Stream에 대해 배우면서, 잘 알아두기만 한다면 문자열과 배열 처리가 정말 편해지겠다는 생각을 했다. 그리고 실제로 그랬다. 더 이상 조건문 떡칠을 할 필요가 없어졌다.

DTO 객체?

Layer간 값을 전달할 때 DTO 객체를 써보란 조언을 받고 적용시켜 봤는데 해시맵을 쓰는 것보다 직관적이고 편하게 구현이 가능했다.

카드를 섞자!

막혀서 가장 오랫동안 고민한 부분이 카드를 랜덤으로 제공하는 부분이었다. CardFactory.Create() 를 통해 제공되는 수정할 수 없는 List<Card> 형태로 제공되는 카드 뭉치를 처음에는 그냥 인덱스를 랜덤으로 돌려 사용할까도 생각해 봤는데, 그렇게 하면 이미 뽑은 카드를 중복으로 또 뽑을 수 있게 되는 문제가 생긴다.

Collections.shuffle()을 쓰면 리스트를 섞을 수 있다는 말을 듣고 그럼 리스트 뒤에서부터 한 장씩 뽑아 없애면 되겠구나 하고 시도해 봤지만 에러가 났다. 새로운 변수에 대입해서 써 보아도 마찬가지였다.

여기서 인터넷을 찾아보다 자바의 얕은 복사와 깊은 복사 개념을 학습하게 되었다. 깊은 복사로 카드 뭉치를 복사하고 섞은 다음 뽑으니 완벽하게 잘 작동했다.

카드가 부족하면 어떡하지?

처음엔 예외 처리를 하려고 했는데, 블랙잭 룰을 찾아보다 보니 국내 카지노에서는 6명, 나무위키에 따르면 8명으로 플레이 인원을 제한하고 있었다. 플레이어 수가 일정 이하로 제한되면 원천적으로 카드 뭉치를 끝까지 뽑을 수가 없으므로 8명 제한을 걸어 카드가 부족할 일이 없게 만들었다.

누가 이겼을까?

이 부분은 솔직히 만족할만큼 깔끔하게 로직을 짜진 못한 것 같다.

조건이득
딜러가 플레이어보다 21에 가까운 경우-100%
플레이어가 딜러보다 21에 가까운 경우+100%
플레이어와 딜러가 같은 점수인 경우0%
딜러만 블랙잭인 경우-100%
플레이어만 블랙잭인 경우+150%
둘 다 블랙잭인 경우0%
딜러만 버스트된 경우+100%
플레이어만 버스트된 경우-100%
둘 다 버스트된 경우0%

회고…

사실 이번 미션도 개인적인 욕심으로 100점을 주긴 힘들 것 같다. DTO 객체도 부분적으로만 사용했고, getter도 사용해버렸다. 인터넷에서 읽어봤던 수많은 글들은 아직도 볼 때마다 새로운 게 나오는 느낌이다. 최종 코딩테스트 때까지 더 열심히 공부해야겠다.

댓글 남기기