diff --git a/docs/README.md b/docs/README.md index e69de29bb..d1b07b478 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,22 @@ +# 기능 목록 + +## 입력 +- 경주할 자동차의 이름 ( 5자 이하, 중복 x) +- 시도 횟수 (숫자) +- #### 단, 잘못된 값을 입력하면 IllegalArgumetException 발생 후 강제 종료 + +## 문구 출력 +- 입력 시 ) +- 자동차 이름 입력 문구 +- 시도 횟수 입력 문구 +- 실행 결과 출력 시 ) +- 각 라운드 당 결과 출력 +- 최종 우승자 문구 출력 ( 단독 / 공동 ) + +## 게임 과정 +- 자동차 이름과 시도 횟수 입력 받기 (검증) +- 0~9 무작위 값 구하기 +- 해당 무작위 값이 4이상인지 판단하기 +- 4이상이면 한 칸 전진(+) +- 각 라운드 수행 시 결과 반영 +- 모든 라운드 수행 시 최종 결과 판단 \ No newline at end of file diff --git a/src/main/kotlin/racingcar/Application.kt b/src/main/kotlin/racingcar/Application.kt index 0d8f3a79d..8a9465012 100644 --- a/src/main/kotlin/racingcar/Application.kt +++ b/src/main/kotlin/racingcar/Application.kt @@ -1,5 +1,8 @@ package racingcar +import racingcar.controller.Controller + fun main() { - // TODO: 프로그램 구현 + val controller = Controller() + controller.start() } diff --git a/src/main/kotlin/racingcar/controller/Controller.kt b/src/main/kotlin/racingcar/controller/Controller.kt new file mode 100644 index 000000000..e88c2fda0 --- /dev/null +++ b/src/main/kotlin/racingcar/controller/Controller.kt @@ -0,0 +1,56 @@ +package racingcar.controller + +import racingcar.domain.Car +import racingcar.view.InputView +import racingcar.view.OutputView +import racingcar.domain.Validator + +class Controller { + private val inputView = InputView() + private val outputView = OutputView() + fun start() { + val inputCarsNames = readInputCars() + val inputRound = readInputRound().toInt() + Validator(inputCarsNames, inputRound) + val carList: List = inputCarsNames.split(',').map { Car(it) } + outputView.printStartOfResultPhrase() + + repeat(inputRound) { + playGame(carList) + showResult(carList) + println() + } + + showWinners(carList) + + inputView.finish() + } + + private fun readInputCars(): String { + outputView.printInputNameOfCars() + return inputView.readUserInput() + } + + private fun readInputRound(): String { + outputView.printInputNumberOfAttempts() + return inputView.readUserInput() + } + + private fun playGame(carList: List) { + carList.forEach { it.goForward() } + } + + private fun showResult(carList: List) { + carList.forEach { outputView.printRoundResultFormat(it.carName, it.forward) } + } + + private fun showWinners(carList: List) { + outputView.printWinnerFormat(decideWinners(carList)) + } + + private fun decideWinners(carList: List): List { + val maxForward = carList.maxByOrNull { it.forward }?.forward + return carList.filter { it.forward == maxForward }.map { it.carName } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/racingcar/domain/Car.kt b/src/main/kotlin/racingcar/domain/Car.kt new file mode 100644 index 000000000..62d5f7f38 --- /dev/null +++ b/src/main/kotlin/racingcar/domain/Car.kt @@ -0,0 +1,13 @@ +package racingcar.domain + +class Car(name: String) { + var carName: String = name.trim() + var forward = 0 + + + fun goForward() { + if (DecisionMaker().decideToMove()) { + forward++ + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/racingcar/domain/Cars.kt b/src/main/kotlin/racingcar/domain/Cars.kt new file mode 100644 index 000000000..f2e97b275 --- /dev/null +++ b/src/main/kotlin/racingcar/domain/Cars.kt @@ -0,0 +1,18 @@ +package racingcar.domain + +class Cars(private val cars: List) { + + private val stateOfGame = cars.associate { it.carName to it.forward }.toMutableMap() + fun moveCar(name: String) { + stateOfGame[name] = stateOfGame[name]!! + 1 + } + + companion object { + fun registerCars(userInput: String) { + val carNames = userInput.split(",") + val cars = carNames.map { Car(it) } + Cars(cars) + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/racingcar/domain/DecisionMaker.kt b/src/main/kotlin/racingcar/domain/DecisionMaker.kt new file mode 100644 index 000000000..139f06cf3 --- /dev/null +++ b/src/main/kotlin/racingcar/domain/DecisionMaker.kt @@ -0,0 +1,17 @@ +package racingcar.domain + +import camp.nextstep.edu.missionutils.Randoms + +class DecisionMaker() { + + companion object { + const val MIN_DIGIT = 0 + const val MAX_DIGIT = 9 + const val FORWARD_THRESHOLD = 4 + } + + fun decideToMove(): Boolean { + val random = Randoms.pickNumberInRange(MIN_DIGIT, MAX_DIGIT) + return random >= FORWARD_THRESHOLD + } +} \ No newline at end of file diff --git a/src/main/kotlin/racingcar/domain/Validator.kt b/src/main/kotlin/racingcar/domain/Validator.kt new file mode 100644 index 000000000..b8af3d8a8 --- /dev/null +++ b/src/main/kotlin/racingcar/domain/Validator.kt @@ -0,0 +1,43 @@ +package racingcar.domain + +class Validator(inputCarsNames: String, inputRound: Int) { + + init { + validateLength(inputCarsNames) + validateDuplicate(inputCarsNames) + validateNumOfCars(inputCarsNames) + validateRound(inputRound) + } + + companion object { + + private fun validateLength(inputCarsNames: String) { + val carList = inputCarsNames.split(',').toList() + carList.forEach { car -> + if (car.length > 5) { + throw IllegalArgumentException("이름은 5자 이하만 가능합니다.") + } + } + } + + private fun validateDuplicate(inputCarsNames: String) :String { + val carList = inputCarsNames.split(',').toList() + require(HashSet(carList).size != carList.size) { "중복된 이름이 존재하면 안 됩니다."} + + return this.toString() + } + + private fun validateNumOfCars(inputCarsNames: String) { + val carList = inputCarsNames.split(',').toList() + if (carList.size == 1) { + throw IllegalArgumentException("2대 이상의 차가 필요합니다.") + } + } + + private fun validateRound(inputRound: Int) { + if (inputRound < 0 || inputRound > 100) { + throw IllegalArgumentException("1 ~ 100 범위의 숫자만 입력 가능합니다.") + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/racingcar/view/InputView.kt b/src/main/kotlin/racingcar/view/InputView.kt new file mode 100644 index 000000000..28a3d21ef --- /dev/null +++ b/src/main/kotlin/racingcar/view/InputView.kt @@ -0,0 +1,10 @@ +package racingcar.view + +import camp.nextstep.edu.missionutils.Console + +class InputView { + + fun readUserInput(): String = Console.readLine() + + fun finish() = Console.close() +} \ No newline at end of file diff --git a/src/main/kotlin/racingcar/view/Message.kt b/src/main/kotlin/racingcar/view/Message.kt new file mode 100644 index 000000000..4ed8295f9 --- /dev/null +++ b/src/main/kotlin/racingcar/view/Message.kt @@ -0,0 +1,13 @@ +package racingcar.view + +class Message(private val message: String) { + override fun toString() = message + + companion object { + const val NAME_OF_CARS = "경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)" + const val NUMBER_OF_ATTEMPTS = "시도할 횟수는 몇 회인가요?" + const val RESULT_PHRASE = "\n실행 결과" + const val FINAL_WINNER_FORMAT = "최종 우승자 : " + } + +} \ No newline at end of file diff --git a/src/main/kotlin/racingcar/view/OutputView.kt b/src/main/kotlin/racingcar/view/OutputView.kt new file mode 100644 index 000000000..3c88f42f2 --- /dev/null +++ b/src/main/kotlin/racingcar/view/OutputView.kt @@ -0,0 +1,22 @@ +package racingcar.view + +import racingcar.domain.Car + + +class OutputView { + + fun printInputNameOfCars() = println(Message.NAME_OF_CARS) + + fun printInputNumberOfAttempts() = println(Message.NUMBER_OF_ATTEMPTS) + + fun printStartOfResultPhrase() = println(Message.RESULT_PHRASE) + + fun printRoundResultFormat(name: String, position: Int) { + println("$name : ${"-".repeat(position)}") + } + + fun printWinnerFormat(winnerList: List) { + val winners = winnerList.joinToString(",") + println("${Message.FINAL_WINNER_FORMAT}${winners}") + } +} \ No newline at end of file diff --git a/src/test/kotlin/racingcar/CarTest.kt b/src/test/kotlin/racingcar/CarTest.kt new file mode 100644 index 000000000..b67531fee --- /dev/null +++ b/src/test/kotlin/racingcar/CarTest.kt @@ -0,0 +1,23 @@ +package racingcar + +import camp.nextstep.edu.missionutils.test.Assertions.assertSimpleTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import racingcar.domain.Car +import racingcar.domain.Validator +class CarTest { + + @Test + fun `이름 중복 예외`() { + assertSimpleTest { + assertThrows { Validator("mark,mark", 3)} + } + } + + @Test + fun `이름 5자 초과 예외`() { + assertSimpleTest{ + assertThrows { Car("chominji") } + } + } +} \ No newline at end of file