-
Notifications
You must be signed in to change notification settings - Fork 313
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Step3 블랙잭(딜러) #785
base: duhanmo
Are you sure you want to change the base?
Step3 블랙잭(딜러) #785
Changes from 14 commits
f301a6c
6658a35
fd73ec1
dee9976
97d81cd
ed857a0
2c50b72
37b72e7
f6331c9
a95919b
6f38f2b
7cff569
823396e
c041297
bca0a30
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,9 @@ | ||
package blackjack | ||
|
||
import blackjack.controller.BlackJackGame | ||
import blackjack.domain.GameTable | ||
import blackjack.controller.BlackjackGame | ||
import blackjack.view.InputView | ||
import blackjack.view.ResultView | ||
|
||
fun main() { | ||
BlackJackGame(GameTable, InputView, ResultView).start() | ||
BlackjackGame(InputView, ResultView).start() | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package blackjack.controller | ||
|
||
import blackjack.domain.Dealer | ||
import blackjack.domain.Deck | ||
import blackjack.domain.GameResult | ||
import blackjack.domain.GameTable | ||
import blackjack.domain.Participant | ||
import blackjack.domain.Player | ||
import blackjack.view.InputView | ||
import blackjack.view.ResultView | ||
|
||
data class BlackjackGame( | ||
private val inputView: InputView, | ||
private val resultView: ResultView, | ||
) { | ||
fun start() { | ||
val gameTable = GameTable(Deck.create()) | ||
val participants = playGame(gameTable) | ||
printCard(participants) | ||
printGameResult(participants) | ||
} | ||
|
||
private fun playGame(gameTable: GameTable): List<Participant> { | ||
val participants = setUpInitCard(gameTable) | ||
val (players, dealer) = Participant.separate(participants) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 딜러와 플레이어를 분리해서 사용한다면, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 네! 피드백 반영하며 수정하였어요😀 플레이어와 딜러의 상위클래스를 따로 두지 않고 플레이어의 기능만을 상속받는 딜러를 구현하였어요😊 |
||
val gamedPlayers = playersTurn(players, gameTable) | ||
resultView.linebreak() | ||
val gamedDealer = dealerTurn(dealer, gameTable) | ||
return gamedPlayers + gamedDealer | ||
} | ||
|
||
private fun setUpInitCard(gameTable: GameTable): List<Participant> { | ||
val participants = gameTable.dealInitCard(getParticipants()) | ||
resultView.linebreak() | ||
resultView.printInitCardReceive(participants) | ||
resultView.printParticipantsCard(participants = participants, printScore = false) | ||
resultView.linebreak() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. resultView에게 책임을 전달해주면 어떨까요? View가 콘솔뷰가 아니라, 웹이나 모바일 화면이라면 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 네! 반영하도록 할게요😄 |
||
return participants | ||
} | ||
|
||
private fun getParticipants(): List<Participant> { | ||
return buildList { | ||
add(Dealer.create()) | ||
addAll(inputView.inputNames().map { Player.create(name = it) }) | ||
} | ||
} | ||
|
||
private fun playersTurn( | ||
participants: List<Participant>, | ||
gameTable: GameTable, | ||
): List<Participant> { | ||
return participants.map { playerTurn(it, gameTable) } | ||
} | ||
|
||
private tailrec fun playerTurn( | ||
player: Participant, | ||
gameTable: GameTable, | ||
): Participant { | ||
if (!player.canHit() || !inputView.inputHit(player)) { | ||
return player | ||
} | ||
val hitPlayer = gameTable.hit(player) | ||
resultView.printParticipantCard(participant = hitPlayer, printScore = false) | ||
return playerTurn(hitPlayer, gameTable) | ||
} | ||
|
||
private tailrec fun dealerTurn( | ||
dealer: Participant, | ||
gameTable: GameTable, | ||
): Participant { | ||
if (!dealer.canHit()) { | ||
return dealer | ||
} | ||
resultView.printDealerHit() | ||
return dealerTurn(gameTable.hit(dealer), gameTable) | ||
} | ||
|
||
private fun printCard(participants: List<Participant>) { | ||
resultView.linebreak() | ||
resultView.printParticipantsCard(participants = participants, printScore = true) | ||
} | ||
|
||
private fun printGameResult(participants: List<Participant>) { | ||
resultView.linebreak() | ||
resultView.printGameResult(GameResult.from(participants)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 해당 로직들은 Controller보다는 View의 책임은 아닐까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 네! View에 위임하도록 할게요👍 |
||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BlackjackGame은 Controller역할을 하고 있어요, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 네! 말씀대로 GameTable을 service로직처럼 상위패키지(controller)로 추출하여 로직을 분담하도록했어요🙂 |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,19 @@ | ||
package blackjack.domain | ||
|
||
import blackjack.domain.Rank.Companion.ACE | ||
import blackjack.domain.Rank.ACE | ||
|
||
data class Card( | ||
val rank: Rank, | ||
val suit: Suit, | ||
) { | ||
val score = rank.score | ||
val score: Int | ||
get() = rank.score | ||
|
||
fun isAce(): Boolean { | ||
return rank == ACE | ||
} | ||
val isAce: Boolean | ||
get() = rank == ACE | ||
|
||
companion object { | ||
val ALL: List<Card> = | ||
Suit.entries.flatMap { suit -> Rank.ALL.map { rank -> Card(rank, suit) } } | ||
Suit.entries.flatMap { suit -> Rank.entries.map { rank -> Card(rank, suit) } } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,26 @@ | ||
package blackjack.domain | ||
|
||
import blackjack.domain.MatchResult.DRAW | ||
import blackjack.domain.MatchResult.LOSS | ||
import blackjack.domain.MatchResult.WIN | ||
|
||
data class Cards(val values: List<Card>) { | ||
val score: Int | ||
get() = calculateScore() | ||
|
||
fun isScoreLowerThanLimit(): Boolean { | ||
return score < BLACKJACK_SCORE_LIMIT | ||
val isBust: Boolean | ||
get() = calculateScore() > BLACKJACK_SCORE_LIMIT | ||
|
||
fun scoreLowerThan(limit: Int): Boolean { | ||
return score < limit | ||
} | ||
|
||
fun compareScore(other: Cards): MatchResult { | ||
return when { | ||
score > other.score -> WIN | ||
score < other.score -> LOSS | ||
else -> DRAW | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 점수가 같더라도, 블랙잭 여부에 따라서 승패가 나눠질수도 있어요!
https://namu.wiki/w/%EB%B8%94%EB%9E%99%EC%9E%AD(%ED%94%8C%EB%A0%88%EC%9E%89%20%EC%B9%B4%EB%93%9C) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 네! 해당부분도 고려하도록 할게요🙂 |
||
} | ||
|
||
fun add(card: Card): Cards { | ||
|
@@ -14,7 +29,7 @@ data class Cards(val values: List<Card>) { | |
|
||
private fun calculateScore(): Int { | ||
val totalScore = values.sumOf { it.score } | ||
var aceCount = values.count { it.isAce() } | ||
var aceCount = values.count { it.isAce } | ||
|
||
var adjustedScore = totalScore | ||
while (adjustedScore > BLACKJACK_SCORE_LIMIT && aceCount > 0) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package blackjack.domain | ||
|
||
import blackjack.domain.MatchResult.DRAW | ||
import blackjack.domain.MatchResult.LOSS | ||
import blackjack.domain.MatchResult.WIN | ||
import blackjack.domain.dto.DealerGameResult | ||
import blackjack.domain.dto.PlayerGameResult | ||
|
||
data class GameResult( | ||
val dealerGameResult: DealerGameResult, | ||
val playerGameResults: List<PlayerGameResult>, | ||
) { | ||
companion object { | ||
fun from(participants: List<Participant>): GameResult { | ||
val (players, dealer) = Participant.separate(participants) | ||
val playerGameResults = players.map { player -> PlayerGameResult(player, player.compareScore(dealer)) } | ||
return GameResult( | ||
DealerGameResult( | ||
winCount = playerGameResults.count { it.result == LOSS }, | ||
lossCount = playerGameResults.count { it.result == WIN }, | ||
drawCount = playerGameResults.count { it.result == DRAW }, | ||
), | ||
playerGameResults, | ||
) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,21 @@ | ||
package blackjack.domain | ||
|
||
object GameTable { | ||
const val INIT_CARD_DRAW_COUNT = 2 | ||
|
||
fun dealInitCard( | ||
users: List<User>, | ||
deck: Deck, | ||
): List<User> { | ||
return users.map { user -> | ||
(1..INIT_CARD_DRAW_COUNT).fold(user) { acc, _ -> | ||
acc.receiveCard(deck.draw()) | ||
data class GameTable( | ||
private val deck: Deck, | ||
) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
먼저 게임테이블에서 참가자목록을 가진 불변 컬렉션으로 만들었다면,
불변성과 객체지향은 조금 다른 이야기라고 볼수 있어요 :) 각각의 장단점을 고려하면서, 요구사항에 따라서 적절히 조화를 하는게 가장 이상적인 방법은 아닐까요? 객체가 상태를 관리해야하는 비즈니스로직이 있다면, 객체가 내부적으로 상태관리를 해주는게 맞지는않을까요? |
||
fun dealInitCard(participants: List<Participant>): List<Participant> { | ||
return participants.map { participant -> | ||
(1..INIT_CARD_DRAW_COUNT).fold(participant) { acc, _ -> | ||
acc.hit(deck.draw()) | ||
} | ||
} | ||
} | ||
|
||
fun hit(participant: Participant): Participant { | ||
return participant.hit(deck.draw()) | ||
} | ||
|
||
companion object { | ||
const val INIT_CARD_DRAW_COUNT = 2 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package blackjack.domain | ||
|
||
enum class MatchResult(val description: String) { | ||
WIN("승"), | ||
LOSS("패"), | ||
DRAW("무"), | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package blackjack.domain | ||
|
||
import blackjack.domain.MatchResult.LOSS | ||
import blackjack.domain.MatchResult.WIN | ||
|
||
sealed class Participant(val name: String, val cards: Cards) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 일반적으로 코틀린에서는 클래스별로 하나의 파일에서 관리되도록 가이드하고 있긴합니다 :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 네! 조언 감사합니다🙇♂️ |
||
val isBust: Boolean | ||
get() = cards.isBust | ||
|
||
abstract fun canHit(): Boolean | ||
|
||
abstract fun hit(card: Card): Participant | ||
|
||
companion object { | ||
fun separate(participants: List<Participant>): Pair<List<Player>, Dealer> { | ||
return participants.filterIsInstance<Player>() to participants.first { it is Dealer } as Dealer | ||
} | ||
} | ||
} | ||
|
||
class Player(name: String, cards: Cards) : Participant(name, cards) { | ||
override fun canHit(): Boolean { | ||
return cards.scoreLowerThan(PLAYER_SCORE_LIMIT) | ||
} | ||
|
||
override fun hit(card: Card): Player { | ||
return Player(this.name, cards.add(card)) | ||
} | ||
|
||
fun compareScore(dealer: Dealer): MatchResult { | ||
return when { | ||
dealer.isBust -> WIN | ||
this.isBust -> LOSS | ||
else -> cards.compareScore(dealer.cards) | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 승패를 결정하는 로직이 Cards, Player 객체에 혼재되어있는건 아닐까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 네! 해당 결정로직이 카드까지 흘러들어가지 않고 (반영후에는 Hand 클래스) 플레이어가 판단하도록 수정하였어요😊 |
||
|
||
companion object { | ||
private const val PLAYER_SCORE_LIMIT = 21 | ||
|
||
fun create(name: String): Player { | ||
return Player(name, Cards(emptyList())) | ||
} | ||
} | ||
} | ||
|
||
class Dealer(cards: Cards) : Participant("딜러", cards) { | ||
override fun canHit(): Boolean { | ||
return cards.scoreLowerThan(DEALER_SCORE_LIMIT) | ||
} | ||
|
||
override fun hit(card: Card): Dealer { | ||
return Dealer(cards.add(card)) | ||
} | ||
|
||
companion object { | ||
private const val DEALER_SCORE_LIMIT = 17 | ||
|
||
fun create(): Dealer { | ||
return Dealer(Cards(emptyList())) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BlackjackGame을 data class 로 정의한 이유가있을까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
일관성을 맞추기 위해 특별한 경우가 아니면 data class 로 선언을 했는데요,
좀더 의미를 가지고 정의를 하도록 할게요🙂
--
추가 반영하며 data class와 일반 class를 나눈 근거는
객체가 직접 자신의 상태를 변경하며 관리하는 클래스는 일반 class,
내부 로직이 없으며 데이터로서의 역할을 하는 클래스는 data class로 선언하였어요!