우선, Player와 Dealer의 공통적 행위 및 요소를 PersonInCasino 추상 클래스(Abstract class)에 정의한다. 각 각의 Player, Dealer class는 PersonInCasino abstract class를 상속받아 공통된 행위에 대한 다른 행동을 method 오버라이드(override)를 통해서 method를 재정의한다. method명은 Blackjack 게임의 룰에 따라 카드의 숫자의 합이 21을 넘지 않는 경우, 카드를 계속 뽑는 경우를 Hit, 멈추는 것을 Stay로 하기 때문에 doHit()doStay()로 정의하였다. 이외에 카드 숫자의 합이 21을 초과하는 경우 Burst라고 한다.
앞의 Handwriting의 Blackjack 게임의 룰에서도 정리를 하였지만, Dealer는 Dealer hit rule이라는 규칙을 가지고 있기 때문에, 카드의 숫자합이 16이하이면 Hit, 17이상이면 무조건 Stay하도록 한다. 이는 상대 Player의 카드 상태와 상관없이 무조건적으로 Dealer가 지켜야하는 Rule이다.
한 번 개인적으로 생각해서 재미있는 기능을 넣어보았는데, 이 기능은 사람 클래스에 각자의 인격을 정의할 수 있는 personality를 적용하는 것이다.
PersonInCasino 추상 클래스에 personality라는 class variable이 있는데, 이는 사람 성격에 따라 21미만의 카드를 뽑은 상태에서 카드를 계속 뽑을지(Hit) 아니면 멈출지(Stay)를 각기 다른 확률을 적용한다. 예를들어 소심한 사람은 20~30%, 평범한 사람은 50%, 욕심이 많은 사람은 80~90%의 확률로 카드를 계속 뽑는 결정을 하게 된다. 구체적인 구현 방법은 Python의 randchoice method에서 주어진 리스트에 대해 각 각 weight를 부여해서 확률을 조정할 수 있는데, 이를 활용해서 각기다른 성격을 가진 사람들이 다른 확률로 Hit을 한다.
Hand class의 ring finger, middle finger, back of hand를 정의한 이유는 게임을 할때 player의 gesture가 있기 때문이다. ring finger(검지)로 테이블을 톡 내려주는 gesture를 취하는 경우는 Hit, 검지와 중지(middle finger + ring finger)를 테이블에 향하여 친 후 손가락을 떼는 gesture는 Split을 의미한다. 그리고 손등(back of hand)이 위로 보이게 해서 손목스냅을 이용해 테이블을 한번 훑어주는 모션은 Stay이다.
has-A 의존관계 중에 클래스간의 응집력이 높은 Composition의 형태로 Hand, Card, Game class로 Player와 Dealer의 대응되는 각 각의 instance variable을 초기화시켜줬다.
Card class는 Player와 Dealer 모두 동일한 카드 뭉치로부터 카드를 받고 있기 때문에 Card class의 cardList는 class 변수로써 선언을 하였다.
가능한 한 설계 단계에서 생각할 수 있는 경우의 수를 토대로 클래스 설계를 해보았다. 좀 더 가시적으로 클래스 관계를 이해할 수 있도록 Class UML(Unified Modeling Language)를 만들어서 첨부하였다.
코드 구현
실제 게임으로 각 Player 객체를 Dealer 객체가 관리하기 위해 설계단계에서는 고려되지 않았던 Player management class(PlayerMgmt)를 새로 추가하였다. 그리고 기존에 설계했던 class의 일부 method를 새로 추가하였다.
# 게임 참가자 수 만큼 카드를 한 장씩 나눠주고 1회 턴이 돌았을때 # 각 게임 참가자의 카드를 검사해서 카드 수의 총 합이 21 초과인 경우 burst player로 구분한다. defpickupAndGiveCardToPlayer(self): # card 뭉치에서 카드를 pop()으로 뽑느다. # 우선 카드정보 리스트를 정의할 필요가 있다. numOfPlayer = self.playerMgmt.playerList for i inrange(len(numOfPlayer)): if self.card.cardList: card = self.card.pickupTheCard() self.playerMgmt.updateCard(i, card) # 카드를 전체적으로 한 장씩 나눠주고, 각 Player의 카드정보를 확인하여, # 카드 숫자의 합이 21을 초과하는 사람은 updateBurstPlayer() method를 # 사용하여 burst player로 구분한다. for j inrange(len(numOfPlayer)): player = self.playerMgmt.checkPlayer(j) cardList = player['cardDeck'].checkCardStatusOfUser() _sum = 0 for n in cardList: if n == 'A': ifsum(cardList) > 10: sum += 1 else: sum += 11 else: sum += n if _sum > 21: burstPlayer = self.playerMgmt.playerList.pop(j) self.playerMgmt.burstPlayerList.append(burstPlayer)
# Card class # 카드의 종류와 상관없이 2~9까지는 각 각 4장씩 넣고 # 10은 10(4장) J,Q,K 각 각 4장씩 총 16장을 넣어준다. # Ace의 경우에는 1또는 11로 계산될 수 있기 때문에 'A'로 4장 넣어준다. classCard: cardList = []
def__init__(self): self.prepareCards()
@classmethod defprepareCards(cls): # 2~9까지 숫자카드를 각 각 4장씩 넣는다. for i inrange(2, 9+1): for _ inrange(4): cls.cardList.append(i) # 숫자카드 10을 16장 넣어준다. for _ inrange(16): cls.cardList.append(10) # Ace 카드를 4장 넣어준다. for _ inrange(4): cls.cardList.append('A') # 카드를 섞어준다. cls.__shuffleCards()
# Player객체를 일괄적으로 관리하기 위한 Player management class classPlayerMgmt: playerList = [] burstPlayerList = []
# index번째 player의 객체 정보를 참조할 수 있도록 객체를 반환한다. @classmethod defcheckPlayer(cls, index): if cls.playerList[index]: return cls.playerList[index] else: print("해당 Player는 존재하지 않습니다.")
@classmethod defupdateBurstPlayer(cls, index): # list의 index는 0번째부터 시작하기 때문에 -1을 해서 index-1번째 player를 pop한 뒤에 # burstPlayer list에 업데이트를 해준다. if cls.playerList[index-1]: burstPlayer = cls.playerList.pop(index-1) else: print("해당 Player는 존재하지 않습니다.")