Skip to content
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

[자동차 경주] 이아름 미션 제출합니다. #211

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# 구현할 기능 목록

### 게임 시작

- 자동차 이름 입력
- 시도 횟수 입력
- 경주 중 현황 출력
- 우승자들 결정
- 우승자들 출력

### 자동차 이름 입력

- 경주 자동차 이름 입력 메시지 출력(`경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)`)
- 쉼표(,)를 기준으로 구분
- 자동차 이름으로 객체 생성 후 `List`에 담아서 반환
- 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`을 발생시킨 후 애플리케이션은 종료

### 시도 횟수 입력

- 시도 횟수 입력 메시지 출력(`시도할 횟수는 몇 회인가요?`)
- 입력받은 횟수 반환
- 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`을 발생시킨 후 애플리케이션은 종료

### 경주 중 현황

- 경기 결과 메시지 출력(`실행 결과`)
- 횟수별 경기 진행
- 횟수별 경주 중 현황 출력

### 경기 진행

- 매개 변수로 받은 자동차 목록을 순회
- `pickNumberInRange()`를 활용하여 0에서 9 사이에서 무작위 값을 구하기

### 자동차 전진 또는 멈춤

- 매개 변수로 받은 랜덤 숫자가 4 이상일 경우는 전진, 아니면 멈춤

### 경주 중 현황 출력

- 매개 변수로 받은 자동차 목록을 순회하며 자동차 전진의 결과를 출력, 전진 거리는 `-`으로 표시(ex. `pobi : ---`)

### 우승자들 결정

- 매개 변수로 받은 자동차 목록 중 자동차 주행 거리의 최대값을 구하기
- 자동차 목록에서 최대값과 주행거리가 같은 자동차 이름을 `List`에 담아서 반환

### 우승자들 출력

- 우승자 메시지 출력(`최종 우승자 :`)
- 매개 변수로 받은 우승자들 목록 출력
- 우승자는 한 명 이상일 수 있음

---

# 객체

### 자동차

- `name` : String 타입으로 자동차 이름
- 이름은 1자 이상, 5자 이하만 가능
- `distance` : 자동차가 간 거리를 `-`로 표현한 String
- `distanceLength` : `distance`의 길이
- `drive()` : `distance`에 `-` 추가
65 changes: 64 additions & 1 deletion src/main/kotlin/racingcar/Application.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,68 @@
package racingcar

import camp.nextstep.edu.missionutils.Console
import camp.nextstep.edu.missionutils.Randoms

fun main() {
// TODO: 프로그램 구현
startGame(Console::readLine, Randoms::pickNumberInRange)
}

fun startGame(readLine: () -> String, pickNumberInRange: (Int, Int) -> Int) {
val carList = inputCarName(readLine)
val tryNumber = inputTryNumber(readLine)

printRacing(carList, tryNumber, pickNumberInRange)

val winnerNameList = decideWinnerNameList(carList)
printWinner(winnerNameList)
}

fun inputCarName(readLine: () -> String): List<Car> {
println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)")
return readLine().split(",").map { Car(it.trim()) }
}

fun inputTryNumber(readLine: () -> String): Int {
println("시도할 횟수는 몇 회인가요?")
return readLine().apply {
require(this.isNotEmpty() && Regex("[0-9]+").matches(this)) { "시도할 횟수는 숫자만 입력해야만 합니다." }
}.toInt()
}

fun printRacing(carList: List<Car>, tryNumber: Int, pickNumberInRange: (Int, Int) -> Int) {
println("\n실행 결과")

for (round in 0 until tryNumber) {
raceCar(carList, pickNumberInRange)
printCurrentRace(carList)
}
}

fun raceCar(carList: List<Car>, pickNumberInRange: (Int, Int) -> Int) {
for (car in carList) {
val randomNumber = pickNumberInRange(0, 9)
driveCar(car, randomNumber)
}
}

fun driveCar(car: Car, randomNumber: Int) {
if (randomNumber >= 4) {
car.drive()
}
}

fun printCurrentRace(carList: List<Car>) {
for (car in carList) {
println("${car.name} : ${car.distance}")
}
println()
}

fun decideWinnerNameList(carList: List<Car>): List<String> {
val max = carList.maxOf { it.distanceLength }
return carList.filter { it.distanceLength == max }.map { it.name }
}

fun printWinner(winnerList: List<String>) {
print("최종 우승자 : ${winnerList.joinToString(", ")}")
}
34 changes: 34 additions & 0 deletions src/main/kotlin/racingcar/Car.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package racingcar

class Car(name: String) {
val name: String
private var _distance: String
val distance: String
get() = this._distance
val distanceLength: Int
get() = distance.length

init {
require(name.length in 1 until 6) { "이름은 1자 이상, 5자 이하만 가능합니다." }
this.name = name
this._distance = ""
}

fun drive() {
this._distance = _distance.plus('-')
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Car) return false
if (name != other.name) return false
if (distance != other.distance) return false
return true
}

override fun hashCode(): Int {
var result = name.hashCode()
result = 31 * result + distance.hashCode()
return result
}
}
20 changes: 20 additions & 0 deletions src/test/kotlin/racingcar/CarTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package racingcar

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows

class CarTest {
@Test
fun `name 1자 미만 5자 초과 예외 처리`() {
assertThrows<IllegalArgumentException> { Car("123456") }
}

@Test
fun `drive 실행 시 distance 1증가`() {
val car = Car("test")
val initDistanceLength = car.distanceLength
car.drive()
assertThat(car.distanceLength).isEqualTo(initDistanceLength + 1)
}
}
147 changes: 147 additions & 0 deletions src/test/kotlin/racingcar/RacingGameTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package racingcar

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import java.io.ByteArrayOutputStream
import java.io.OutputStream
import java.io.PrintStream

class RacingGameTest {
private lateinit var standardOut: PrintStream
private lateinit var captor: OutputStream

@BeforeEach
fun init() {
standardOut = System.out
captor = ByteArrayOutputStream()
System.setOut(PrintStream(captor))
}

@AfterEach
fun printOutput() {
System.setOut(standardOut)
println(output())
}

fun output(): String {
return captor.toString().trim { it <= ' ' }
}

@Test
fun `inputCarName 정상 입력`() {
val input = "a,b,c"
val carList = input.split(",").map { Car(it.trim()) }
assertThat(inputCarName { input }).isEqualTo(carList)
}

@Test
fun `inputCarName 빈칸 입력 예외 발생`() {
val input = ""
assertThrows<IllegalArgumentException> { inputCarName { input } }
}

@Test
fun `inputCarName 5자 이상 입력 예외 발생`() {
val input = "hello,abc,wrongName"
assertThrows<IllegalArgumentException> { inputCarName { input } }
}

@Test
fun `inputTryNumber 정상 입력`() {
val input = "5"
assertThat(inputTryNumber { input }).isEqualTo(5)
}

@Test
fun `inputTryNumber 빈칸 입력 예외 발생`() {
val input = ""
assertThrows<IllegalArgumentException> { inputTryNumber { input } }
}

@Test
fun `inputTryNumber 문자 입력 예외 발생`() {
val input = "a"
assertThrows<IllegalArgumentException> { inputTryNumber { input } }
}

@Test
fun `printRacing 결과 출력`() {
val carList = listOf(Car("a"), Car("b"), Car("c"))
val tryNumber = 1
val pickNumberInRange = { _: Int, _: Int -> 4 }
printRacing(carList, tryNumber, pickNumberInRange)
for (car in carList) {
val expectedOutput = "${car.name} : ${"-".repeat(tryNumber)}"
assertThat(output()).contains(expectedOutput)
}
}

@Test
fun `raceCar 정상 처리`() {
val carList = listOf(Car("a"), Car("b"), Car("c"))
val pickNumberInRange = { _: Int, _: Int -> 4 }
raceCar(carList, pickNumberInRange)
for (car in carList) {
assertThat(car.distanceLength).isEqualTo(1)
}
}

@Test
fun `driveCar 랜덤 숫자 4이상 정상 처리`() {
val car = Car("a")
val randomNumber = 4
driveCar(car, randomNumber)
assertThat(car.distanceLength).isEqualTo(1)
}

@Test
fun `driveCar 랜덤 숫자 4미만 정상 처리`() {
val car = Car("a")
val randomNumber = 2
driveCar(car, randomNumber)
assertThat(car.distanceLength).isEqualTo(0)
}

@Test
fun `printCurrentRace 결과 출력`() {
val carList = listOf(Car("a"), Car("b"), Car("c"))
printCurrentRace(carList)
assertThat(output()).contains("a : ", "b : ", "c :")
}

@Test
fun `decideWinnerNameList 한 명 정상 처리`() {
val carList = listOf(Car("a"), Car("b"), Car("c"))
carList[0].drive()
val expectList = listOf("a")
assertThat(decideWinnerNameList(carList)).isEqualTo(expectList)
}

@Test
fun `decideWinnerNameList 여러 명 정상 처리`() {
val carList = listOf(Car("a"), Car("b"), Car("c"))
carList[0].drive()
carList[1].drive()
val expectList = listOf("a", "b")
assertThat(decideWinnerNameList(carList)).isEqualTo(expectList)
}

@Test
fun `printWinner 한 명 결과 출력`() {
val winnerList = listOf("a")
printWinner(winnerList)
val expectedOutput = "최종 우승자 : a"
assertThat(output()).contains(expectedOutput)
}

@Test
fun `printWinner 여러 명 결과 출력`() {
val winnerList = listOf("a", "b")
printWinner(winnerList)
val expectedOutput = "최종 우승자 : a, b"
assertThat(output()).contains(expectedOutput)
}
}