Skip to content

Commit

Permalink
feat: 산타 게임 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
gusah009 committed Feb 24, 2024
1 parent 0cc45d5 commit 6d87200
Show file tree
Hide file tree
Showing 13 changed files with 441 additions and 1 deletion.
11 changes: 10 additions & 1 deletion kotlin-christmas/src/main/kotlin/christmas/Application.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
package christmas

import christmas.controller.ChristmasController
import christmas.service.ChristmasService
import christmas.view.InputView
import christmas.view.OutputView

fun main() {
TODO("프로그램 구현")
ChristmasController.run(
InputView,
OutputView,
ChristmasService
)
}
22 changes: 22 additions & 0 deletions kotlin-christmas/src/main/kotlin/christmas/badge/Badge.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package christmas.badge

import christmas.model.Price
import christmas.validation.ChristmasException

enum class Badge(val minBenefitAmount: UInt) {
별(5_000u),
트리(10_000u),
산타(20_000u),
;

companion object {
fun of(benefitAmount: Price) =
when (benefitAmount.value.toUInt()) {
in 0u until 별.minBenefitAmount -> null
in 별.minBenefitAmount until 트리.minBenefitAmount ->
in 트리.minBenefitAmount until 산타.minBenefitAmount -> 트리
in 산타.minBenefitAmount until UInt.MAX_VALUE -> 산타
else -> throw ChristmasException("말도 안되는 혜택 금액입니다. 혜택 금액: $benefitAmount")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package christmas.controller

import christmas.service.ChristmasService
import christmas.view.InputView
import christmas.view.OutputView

object ChristmasController {
fun run(
inputView: InputView,
outputView: OutputView,
christmasService: ChristmasService,
) {
outputView.printWelcomeMessage()
val userOrderInfo = inputView.getUserOrderInfo()
val benefitInfo = christmasService.getBenefitInfo(userOrderInfo)
outputView.printUserEventBenefit(userOrderInfo, benefitInfo)
}
}
109 changes: 109 additions & 0 deletions kotlin-christmas/src/main/kotlin/christmas/event/EventPolicy.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package christmas.event

import christmas.menu.Menu
import christmas.menu.MenuType
import christmas.menu.MenuWithCount
import christmas.model.Price
import christmas.model.UserOrderInfo
import java.time.DayOfWeek
import java.time.LocalDate
import java.time.Month

private const val MIN_EVENT_PRICE = 10_000

enum class EventPolicy {
GiveawayEventPolicy {
override fun isOwnSupported(userOrderInfo: UserOrderInfo) = userOrderInfo.totalPrice >= 120_000

override fun getBenefit(userOrderInfo: UserOrderInfo): EventType =
EventType.Giveaway(MenuWithCount(Menu.샴페인, 1), this.getPolicyName())

override fun getPolicyName() = "증정 이벤트"
},

ChristmasDiscountPolicy {
override fun isOwnSupported(userOrderInfo: UserOrderInfo) =
userOrderInfo.estimatedVisitDate.isBefore(LocalDate.of(2023, 12, 26))

override fun getBenefit(userOrderInfo: UserOrderInfo) =
EventType.Discount(
Price(1000 + 100 * (userOrderInfo.estimatedVisitDate.dayOfMonth - 1)),
this.getPolicyName()
)

override fun getPolicyName() = "크리스마스 디데이 할인"
},

WeekdayDiscountPolicy {
private val discountPricePerMenu = 2023
private val availableDiscountDayOfWeek = listOf(
DayOfWeek.MONDAY,
DayOfWeek.TUESDAY,
DayOfWeek.WEDNESDAY,
DayOfWeek.THURSDAY,
DayOfWeek.FRIDAY
)

override fun isOwnSupported(userOrderInfo: UserOrderInfo) =
availableDiscountDayOfWeek.contains(userOrderInfo.estimatedVisitDate.dayOfWeek)

override fun getBenefit(userOrderInfo: UserOrderInfo) =
EventType.Discount(
Price(userOrderInfo.getSumPriceOfMenu(MenuType.Dessert) * discountPricePerMenu),
this.getPolicyName()
)

override fun getPolicyName() = "평일 할인"
},

WeekendDiscountPolicy {
private val discountPricePerMenu = 2023
private val availableDiscountDayOfWeek = listOf(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY)

override fun isOwnSupported(userOrderInfo: UserOrderInfo) =
availableDiscountDayOfWeek.contains(userOrderInfo.estimatedVisitDate.dayOfWeek)

override fun getBenefit(userOrderInfo: UserOrderInfo) =
EventType.Discount(
Price(userOrderInfo.getSumPriceOfMenu(MenuType.Main) * discountPricePerMenu),
this.getPolicyName()
)

override fun getPolicyName() = "주말 할인"
},

SpecialDiscountPolicy {
private val discountPrice = 1000
private val availableDiscountDay = listOf(
LocalDate.of(2023, 12, 3),
LocalDate.of(2023, 12, 10),
LocalDate.of(2023, 12, 17),
LocalDate.of(2023, 12, 24),
LocalDate.of(2023, 12, 25),
LocalDate.of(2023, 12, 31),
)

override fun isOwnSupported(userOrderInfo: UserOrderInfo) =
availableDiscountDay.contains(userOrderInfo.estimatedVisitDate)

override fun getBenefit(userOrderInfo: UserOrderInfo) =
EventType.Discount(Price(discountPrice), this.getPolicyName())

override fun getPolicyName() = "특별 할인"
},
;

fun isSupported(userOrderInfo: UserOrderInfo) =
userOrderInfo.totalPrice >= MIN_EVENT_PRICE &&
isInEventDate(userOrderInfo) &&
isOwnSupported(userOrderInfo)

private fun isInEventDate(userOrderInfo: UserOrderInfo) =
userOrderInfo.estimatedVisitDate.year == 2023 && userOrderInfo.estimatedVisitDate.month == Month.DECEMBER

protected abstract fun isOwnSupported(userOrderInfo: UserOrderInfo): Boolean

abstract fun getBenefit(userOrderInfo: UserOrderInfo): EventType

protected abstract fun getPolicyName(): String
}
16 changes: 16 additions & 0 deletions kotlin-christmas/src/main/kotlin/christmas/event/EventType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package christmas.event

import christmas.menu.MenuWithCount
import christmas.model.Price

sealed class EventType(
private val benefitAmount: Price,
private val eventPolicyName: String
) {
class Discount(val price: Price, eventPolicyName: String) : EventType(price, eventPolicyName)

class Giveaway(val menuWithCount: MenuWithCount, eventPolicyName: String) :
EventType(menuWithCount.benefitAmount, eventPolicyName)

override fun toString() = "${eventPolicyName}: ${benefitAmount.toMinusString()}"
}
34 changes: 34 additions & 0 deletions kotlin-christmas/src/main/kotlin/christmas/menu/Menu.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package christmas.menu

import christmas.model.Price

enum class Menu(val price: Price, val menuType: MenuType) {
양송이스프(Price(6_000), MenuType.Appetizer),
타파스(Price(5_500), MenuType.Appetizer),
시저샐러드(Price(8_000), MenuType.Appetizer),
티본스테이크(Price(55_000), MenuType.Main),
바비큐립(Price(54_000), MenuType.Main),
해산물파스타(Price(35_000), MenuType.Main),
크리스마스파스타(Price(25_000), MenuType.Main),
초코케이크(Price(15_000), MenuType.Dessert),
아이스크림(Price(5_000), MenuType.Dessert),
제로콜라(Price(3_000), MenuType.Drink),
레드와인(Price(60_000), MenuType.Drink),
샴페인(Price(25_000), MenuType.Drink),
;

}

enum class MenuType {
Appetizer,
Main,
Dessert,
Drink,
;
}

data class MenuWithCount(val menu: Menu, val count: Int) {
val benefitAmount = menu.price * count

override fun toString() = "${this.menu.name} ${this.count}"
}
27 changes: 27 additions & 0 deletions kotlin-christmas/src/main/kotlin/christmas/model/Price.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package christmas.model

import christmas.validation.requireChristmas
import java.text.DecimalFormat

@JvmInline
value class Price(val value: Int) {
init {
requireChristmas(value >= 0)
}

operator fun times(other: Int) = Price(this.value * other)

operator fun plus(other: Price) = Price(this.value + other.value)

operator fun minus(other: Price) = Price(this.value - other.value)

operator fun compareTo(other: Price) = this.value.compareTo(other.value)

operator fun compareTo(other: Int) = this.value.compareTo(other)

fun toMinusString() = "${DecimalFormat("#,###").format(-value)}"

override fun toString() = "${DecimalFormat("#,###").format(value)}"
}

fun Int.toPrice() = Price(this)
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package christmas.model

import christmas.badge.Badge
import christmas.event.EventType

class UserBenefitInfo(
val benefitList: List<EventType>,
) {
val giveaway: EventType.Giveaway? = benefitList
.filterIsInstance<EventType.Giveaway>().firstOrNull()

val totalBenefitAmount: Price = benefitList.sumOf {
when (it) {
is EventType.Giveaway -> it.menuWithCount.menu.price.value * it.menuWithCount.count
is EventType.Discount -> it.price.value
}
}.toPrice()

val totalDiscountAmount: Price = benefitList
.filterIsInstance<EventType.Discount>()
.sumOf { it.price.value }
.toPrice()

val eventBadge = Badge.of(totalBenefitAmount)
}
38 changes: 38 additions & 0 deletions kotlin-christmas/src/main/kotlin/christmas/model/UserOrderInfo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package christmas.model

import christmas.menu.MenuType
import christmas.menu.MenuWithCount
import christmas.validation.requireChristmas
import java.time.LocalDate

private const val MAX_AVAILABLE_MENU_COUNT = 20

class UserOrderInfo(
val userOrderMenu: UserOrderMenu,
estimatedVisitDay: VisitDay,
) {
val estimatedVisitDate: LocalDate = LocalDate.of(2023, 12, estimatedVisitDay.day)
val totalPrice: Price = userOrderMenu.getTotalPrice()

fun getSumPriceOfMenu(menuType: MenuType) =
this.userOrderMenu.menus.sumOf { if (it.menu.menuType == menuType) it.count else 0 }

@JvmInline
value class VisitDay(val day: Int) {
init {
requireChristmas(day in 1..31) { "유효하지 않은 날짜입니다. 다시 입력해 주세요." }
}
}

@JvmInline
value class UserOrderMenu(val menus: List<MenuWithCount>) {
init {
requireChristmas(menus.sumOf { it.count } <= MAX_AVAILABLE_MENU_COUNT) { "totalMenuCount는 ${MAX_AVAILABLE_MENU_COUNT}개를 넘을 수 없습니다." }
requireChristmas((menus.all { it.menu.menuType == MenuType.Drink }).not()) { "음료만 주문할 수 없습니다. 주문 메뉴: $menus" }
}

fun getTotalPrice() = this.menus.sumOf { it.menu.price.value * it.count }.toPrice()

override fun toString() = menus.joinToString("\n")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package christmas.service

import christmas.event.EventPolicy
import christmas.event.EventType
import christmas.model.UserBenefitInfo
import christmas.model.UserOrderInfo

object ChristmasService {

private val eventPolicyList = EventPolicy.entries

fun getBenefitInfo(userOrderInfo: UserOrderInfo) = UserBenefitInfo(getBenefitList(userOrderInfo))

private fun getBenefitList(userOrderInfo: UserOrderInfo) =
mutableListOf<EventType>().apply {
for (eventPolicy in eventPolicyList) {
if (eventPolicy.isSupported(userOrderInfo)) {
this.add(eventPolicy.getBenefit(userOrderInfo))
}
}
}.toList()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package christmas.validation

const val ERROR_PREFIX = "[ERROR] "

fun requireChristmas(value: Boolean) {
requireChristmas(value) { "Failed requirement." }
}

inline fun requireChristmas(value: Boolean, lazyMessage: () -> Any) {
if (!value) {
throw ChristmasException(lazyMessage().toString())
}
}

class ChristmasException(message: String) : IllegalArgumentException() {
override val message = ERROR_PREFIX + message
}
Loading

0 comments on commit 6d87200

Please sign in to comment.