Skip to content

Commit

Permalink
Step2 - 블랙젝 (#672)
Browse files Browse the repository at this point in the history
* Refactor: Skills, Language의 builder를 사용하여 가독성 향상

* Docs: 기능 목록 초기 구성

* Refactor: Skills, language 구현 변경

* Feat: 카드 번호, 카드 무늬 유효성 검증 구현

Docs: 딜러 역할 삭제, 딜러 역할 분배

* Feat: 카드 포인트 반환 기능 구현, 카드 무늬 명 반환 기능 구현

Refactor: Suit, CardNumber / class -> Enum으로 변경, 카드 무늬, 카드 번호 검증 로직 제거

* Feat: 카드 클래스 생성 및 카드 구성 데이터 캐싱

chore: 카드 관련 클래스 패키지 생성 및 이동

* Feat: 카드팩 클래스 생성 및 캐싱 구현

* Docs: 블랙젝 게임 기능 추가 (카드팩 기능 이동), 플레이어 기능 목록 변경

Refactor: 카드 번호 '10' 추가, 관련 테스트 수정

* Docs: 블랙젝 게임 기능 목록 추가.

Refactor: 카드팩 object -> class 변경, 사이드 이팩트 수정

* Feat: 블랙젝 게임 클래스 생성, 카드팩 검증 로직 구현(52장, 중복X)

* Docs: 카드인텍스 기능 목록 추가.

Feat: 카드 인덱스 증가, 현재 인덱스 반환 기능 구현

* Docs: 카드 인텍스 검증 기능 목록 추가

Feat: 카드 인덱스 검증 로직 구현

* Feat: 카드 배포 구현

* Feat: 플레이어 상태 추가, 플레이어 상태 추가 및 변경 구현

* Feat: 플레이어 카드 저장 기능 구현

* Refactor: CardNumber 명칭 추가, class PlayingCard @OverRide toString(), 카드 셔플 기능 역할 변경 (블렉젝 게임 -> 카드팩 )

* Refactor: 블랙젝 게임 디폴트 파라미터 추가 - playerList: List<Player>

Feat: 추가된 사용자 검증 로직 추가.

* Docs: 블랙젝 게임 기능 추가, 블랙젝 계산기 추가.

* Feat: 카드 초기 세팅 기능 추가(게임 시작시 카드 2장 받음)

Refactor: 사용자 이름 추가.

style: CardNumber 컨벤션 수정

* Refactor: CardIndex -> GameIndex로 역할 범위 확장. Index 값 생성자 파라미터 -> Backing Property 변경

* Refactor: 참여 가능 플레이어 조건 변경, 카드팩 검증 로직 역할 변경(BlackjackGame -> CardPack), 첫 카드 배포 인덱스 변경(플레이어 별 2장은 기본)

* Chore: 프로덕트 클래스, 테스트 클래스 패키지 구조 동기화

* Feat: 블랙젝 계산기 추가(승리에 유리한 계산 결과 반환)

Refactor: PlayingCard의 Ace계산 로직 제거

* Refactor: CardNumber파라미터 접근 제한 변경 private -> publid

* Docs: 기능 목록 체크

* Refactor: GameIndex의 index 변수 명 변경 -> cardIndex

* Feat: 게임 인덱스, 플레이어 인덱스 추가 및 증가 기능 구현

Refactor: 게임 인덱스의 파라미터 maxIndex ->. maxCardIndex로 변경

* Feat: 플레이어 카드 저장 함수 추가

Docs: 블랙젝 게임 기능 목록 추가.

*  Feat: 플레이어 상태 업데이트 기능 구현

* Feat: PlayerGroup 일급 컬렉션 추가,
블랙젝 게임 역할 이동(-> PlayerGroup)
- 참여 인원 검증
- 플레이어 턴 넘김

* Refactor: 카드 반환 기능 CardPack으로 이동

* Refactor: 카드 배포 역할 변경(BlackjackGame -> CardPack), 게임 진행 로직 변경

* Refactor: 불필요한 코드 제거 및 수정

* Refactor
- 클래스명 변경 CardNumber -> CardRank
- CardRank프로퍼티명 일부 변경 cardName -> symbol

* Feat: CardDeck생성(카드 캐싱)

* Refactor:
- GameIndex, CardPack 제거,
- 사이드 이팩트 수정

Feat:
- Class Cards 추가  (PlayingCard기반 일급 컬랙션)
- CardPack의 카드 저장 역할 마이그래이션 CardPack -> Cards

* Refactor:
- 카드덱 인터페이스화(게임 덱, 플레이어 덱)
- 테스트 코드 사이드 이팩트 수정

* Feat: 플레이어 카드 점수 계산 기능 추가.

* Refactor:
- 플레이어 상태 추가
- 플레이어 상태 업데이트 조건문 추가.
- BlackJackCalculator삭제

* chore
- PlayingCard class -> data class
- class Player 불필요한 함수 제거
- class Player 변수 변경 cardList -> playerDeck

* Refactor: 플레이어 상태 업데이트 로직 변경

* chore: GameDeckTest 주석 추가.

* Feat: Input, Output View 구현

* Feat: InputData to PlayerList 를 위한 플래이어 팩토리 구현

chore: 플레이어 수 검증 예외 메시지 수정, InputView 매직 스트링 제거 및 함수 네이밍 변경

* Docs: README.md 변경

Refactor: BlackjackGameTest 삭제

* Refactor: 게임 덱 셔플 기능 추가, 생성자 public으로 변경, 플레이어 덱 override toString()

* Refactor: 블랙잭 게임 로직 재구현

* Refactor: 플레이어 관련 불필요한 함수 제거 및 매직 넘버 제거

* Feat: 메인 함수 구현

* style: 코드 컨벤션 정리 (feat: Gradle)

* Refactor: CardDeck interface 삭제, 플레이어 생성자 추가, 카드팩 재사용, 게임덱 리셋 기능 구현 등 리뷰 구현

* Chore: 클래스 명 변경 GameDeck -> CardDeck

* Refactor: ViewInterfacd생성 및 BlackjackGame 생성자 추가.

* BlackjackGame 생성자로 ViewInterFace 추가.
  • Loading branch information
oyj7677 authored Dec 8, 2023
1 parent 8b9542b commit 320b91c
Show file tree
Hide file tree
Showing 27 changed files with 787 additions and 49 deletions.
55 changes: 54 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,54 @@
# kotlin-blackjack
# kotlin-blackjack

## 기능 요구 사항

블랙잭 게임을 변형한 프로그램을 구현한다. 블랙잭 게임은 딜러와 플레이어 중 카드의 합이 21 또는 21에 가장 가까운 숫자를 가지는 쪽이 이기는 게임이다.

- 카드의 숫자 계산은 카드 숫자를 기본으로 하며, 예외로 Ace는 1 또는 11로 계산할 수 있으며, King, Queen, Jack은 각각 10으로 계산한다.
- 게임을 시작하면 플레이어는 두 장의 카드를 지급 받으며, 두 장의 카드 숫자를 합쳐 21을 초과하지 않으면서 21에 가깝게 만들면 이긴다. 21을 넘지 않을 경우 원한다면 얼마든지 카드를 계속 뽑을 수 있다.

## 기능 목록

### 플레이어

- [O] 플레이어의 초기 상태는 START이다.
- [O] 플레이어가 카드 받는 것을 끝내면 상태는 STAND가 된다.
- [O] 플레이어는 받은 카드를 저장한다.
- [O] 플레이어는 상태를 업데이트 한다.
- [O] 카드의 합이 20 이하 라면 PLAYING
- [O] 카드의 합이 21을 이상 이라면 BUST
- [O] 카드를 받지 않는다 하면 STAND
- [O] 첫 2장의 카드 합이 21이라면 BLACK_JACK

### 플레이어 그룹

- [O] 플레이어는 최소 2명 이상 26명 이하여야 한다.

### 플레이어 펙토리
- [O] 문자열을 받아서 플레이어 리스트 생성.
- [O] 공백은 플레어어로 생성하지 않는다.

### 트럼프 카드(플레잉 카드)

- [O] 카드의 포인트를 반환한다.
- [O] 카드의 무늬 명을 반환한다.
- [O] 카드의 번호를 반환한다.

### 게임 덱
- [O] 카드를 순차적으로 반환한다.

### 플레이어 덱
- [O] 카드를 추가할 수 있다.
- [O] 점수를 반환한다.
- [O] A는 블랙잭에 유리하게 점수를 반영한다.

### inputView

- [O] 플레이어를 입력받는다.
- [O] 플레이어가 입력되지 않으면 예외를 던진다.

### output

- [O] 게임 시작 시 초기 카드 출력
- [O] 플레이어가 가지고 있는 카드를 출력
- [O] 플레이어 카드 계산값을 출력
60 changes: 60 additions & 0 deletions src/main/kotlin/BlackjackGame.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import card.deck.CardDeck
import player.Player
import player.PlayerGroup
import player.Status
import view.InputViewInterface
import view.OutputViewInterface

class BlackjackGame(
private val cardDeck: CardDeck,
val playerGroup: PlayerGroup,
private val inputView: InputViewInterface,
private val outputView: OutputViewInterface,
) {

init {
for (player in playerGroup.playerList) {
settingCard(player)
}
}

fun start() {
for (player in playerGroup.playerList) {
gamePlay(player)
}
}

private fun gamePlay(player: Player) {
while (player.status == Status.PLAYING) {
val response = inputView.askForHit(player.name)
handleForResponse(response, player)
outputView.showPlayingCard(player)
}
}

private fun handleForResponse(response: String, player: Player) {
when (response.uppercase()) {
TEXT_ANSWER_YES -> {
player.saveCard(cardDeck.getCardWithIncrease())
}
TEXT_ANSWER_NO -> {
player.playDone()
}
else -> {
println(TEXT_RETRY_INPUT)
}
}
}

private fun settingCard(player: Player) {
repeat(2) {
player.saveCard(cardDeck.getCardWithIncrease())
}
}

companion object {
private const val TEXT_RETRY_INPUT = "Y 혹은 N만 입력 가능합니다."
private const val TEXT_ANSWER_YES = "Y"
private const val TEXT_ANSWER_NO = "N"
}
}
21 changes: 21 additions & 0 deletions src/main/kotlin/BlackjackMain.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import card.CardPack
import card.deck.CardDeck
import player.PlayerFactory
import player.PlayerGroup
import view.InputView
import view.OutputView

fun main() {

val playerNames = InputView.inputPlayerName()
val playerGroup = PlayerGroup(PlayerFactory.createPlayerList(playerNames))
val cardDeck = CardDeck(CardPack.cards.toMutableList())

val game = BlackjackGame(cardDeck = cardDeck, playerGroup = playerGroup, InputView, OutputView)

OutputView.showGameStart(game.playerGroup)

game.start()

OutputView.showGameEnd(game.playerGroup)
}
9 changes: 9 additions & 0 deletions src/main/kotlin/card/CardPack.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package card

object CardPack {
val cards: List<PlayingCard> = CardRank.values().flatMap { rank ->
Suit.values().map { suit ->
PlayingCard.of(suit = suit, cardRank = rank)
}
}
}
17 changes: 17 additions & 0 deletions src/main/kotlin/card/CardRank.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package card

enum class CardRank(val point: Int, val symbol: String) {
ACE(1, "A"),
TWO(2, "2"),
TREE(3, "3"),
FOUR(4, "4"),
FIVE(5, "5"),
SIX(6, "6"),
SEVEN(7, "7"),
EIGHT(8, "8"),
NINE(9, "9"),
TEN(10, "10"),
JACK(10, "J"),
QUEEN(10, "Q"),
KING(10, "K");
}
35 changes: 35 additions & 0 deletions src/main/kotlin/card/PlayingCard.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package card

data class PlayingCard(val suit: Suit, val cardRank: CardRank) {
fun getPoint(): Int {
return cardRank.point
}

fun getSuitName(): String {
return suit.koName
}

override fun toString(): String {
return "${cardRank.symbol}${suit.koName}"
}

companion object {
private val CARDS: MutableMap<String, PlayingCard> = mutableMapOf()

fun of(suit: Suit, cardRank: CardRank): PlayingCard {
return CARDS[toKey(suit, cardRank)] ?: throw NoSuchElementException()
}

private fun toKey(suit: Suit, cardRank: CardRank): String {
return suit.name + cardRank.name
}

init {
for (suit in Suit.values()) {
for (cardRank in CardRank.values()) {
CARDS[toKey(suit, cardRank)] = PlayingCard(suit, cardRank)
}
}
}
}
}
7 changes: 7 additions & 0 deletions src/main/kotlin/card/Suit.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package card
enum class Suit(val koName: String) {
SPADE("스페이드"),
HEART("하트"),
DIAMOND("다이아몬드"),
CLUB("클러버");
}
26 changes: 26 additions & 0 deletions src/main/kotlin/card/deck/CardDeck.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package card.deck

import card.PlayingCard

class CardDeck(private val cardList: MutableList<PlayingCard>) {

init {
cardList.shuffle()
}

private var index = 0

fun getCardWithIncrease(): PlayingCard {
if (isMaxIndexOfCard()) resetCard()
return cardList[index++]
}

private fun isMaxIndexOfCard(): Boolean {
return index == cardList.size
}

private fun resetCard() {
cardList.shuffle()
index = 0
}
}
50 changes: 50 additions & 0 deletions src/main/kotlin/card/deck/Hands.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package card.deck

import card.CardRank
import card.PlayingCard

class Hands(val cardList: MutableList<PlayingCard> = mutableListOf()) {

fun addCard(playingCard: PlayingCard) {
cardList.add(playingCard)
}

fun getResultPoint(): Int {
var point = cardList.sumOf { it.getPoint() }

if (isContainAce()) {
point += addAcePoint(point)
}

return point
}

fun cardDeckSize() = cardList.size

private fun isContainAce(): Boolean {
return cardList.any { it.cardRank == CardRank.ACE }
}

private fun addAcePoint(point: Int): Int {
return if (point <= 11) {
ADD_ACE_POINT
} else {
ADD_ACE_POINT_NONE
}
}

companion object {
fun create() = Hands(mutableListOf())
private const val ADD_ACE_POINT = 10
private const val ADD_ACE_POINT_NONE = 0
}

override fun toString(): String {
val sb = StringBuilder()
for (card in cardList) {
sb.append("$card, ")
}
sb.deleteCharAt(sb.lastIndex - 1)
return sb.toString()
}
}
52 changes: 52 additions & 0 deletions src/main/kotlin/player/Player.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package player

import card.PlayingCard
import card.deck.Hands

class Player(
val name: String,
status: Status = Status.START,
hands: Hands = Hands(),
) {

var status = status
private set

var playerDeck = hands
private set

fun playDone() {
this.status = Status.STAND
}

fun saveCard(card: PlayingCard) {
playerDeck.addCard(card)
updateStatus()
}

fun updateStatus() {
val totalPoint = playerDeck.getResultPoint()

if (totalPoint > BLACKJACK_NUMBER) {
this.status = Status.BUST
} else if (isBlackJack()) {
this.status = Status.BLACK_JACK
} else {
this.status = Status.PLAYING
}
}

fun getResultPoint(): Int {
return playerDeck.getResultPoint()
}

private fun isBlackJack(): Boolean {
val totalPoint = playerDeck.getResultPoint()
return playerDeck.cardDeckSize() == BLACKJACK_CARD_COUNT && totalPoint == BLACKJACK_NUMBER
}

companion object {
private const val BLACKJACK_CARD_COUNT = 2
private const val BLACKJACK_NUMBER = 21
}
}
19 changes: 19 additions & 0 deletions src/main/kotlin/player/PlayerFactory.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package player

object PlayerFactory {

private const val DELIMITERS = ','

fun createPlayerList(playerNames: String): List<Player> {
val playerNameList = splitInputData(playerNames)
val playerList = mutableListOf<Player>()
playerNameList.forEach { name ->
playerList.add(Player(name))
}
return playerList
}

private fun splitInputData(inputData: String): List<String> {
return inputData.split(DELIMITERS).map { it.trim() }.filter { it.isNotBlank() }
}
}
17 changes: 17 additions & 0 deletions src/main/kotlin/player/PlayerGroup.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package player

class PlayerGroup(val playerList: List<Player>) {

init {
validatePlayer()
}

private fun validatePlayer() {
require(playerList.size in MIN_PLAYER_CNT..MAM_PLAYER_CNT) { "참여 가능한 플레이어의 범위를 넘어섰습니다." }
}

companion object {
private const val MIN_PLAYER_CNT = 2
private const val MAM_PLAYER_CNT = 26
}
}
5 changes: 5 additions & 0 deletions src/main/kotlin/player/Status.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package player

enum class Status {
START, PLAYING, STAND, BLACK_JACK, BUST
}
Loading

0 comments on commit 320b91c

Please sign in to comment.