Skip to content
217 changes: 11 additions & 206 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,210 +1,15 @@
# 미션 - 자동차 경주
구현할 기능 목록

> [!NOTE]
> 이 코드는 원래 [java-racingcar-6](https://github.com/woowacourse-precourse/java-racingcar-6)에서 제공된 **Java 기반의 자동차 경주 게임**을 **Python**에 맞게 변환한 과제입니다. 프로젝트 구조, 요구 사항, 기능 구현 방식은 원본 저장소를 바탕으로 Python 환경에 맞추어 수정하였습니다.
자동차 이름
- 경주할 자동차 이름(,구분)
- 각 자동차 이름 5글자로 제한 넘을 시 valueError

---
시행 횟수
- 경주 시행 횟수 받기

## 🔍 진행 방식
자동차 전진
- 자동차 전진(0,9 랜덤 수 중 4 넘으면 전진 아닐시 그대로 위치)
- 전지시 자동차 이름 출력

- 미션은 **기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항** 세 가지로 구성되어 있습니다.
- 세 가지 요구 사항을 만족하기 위해 노력해야 하며, 특히 기능을 구현하기 전에 기능 목록을 작성하고 기능 단위로 커밋하는 방식으로 진행해야 합니다.
- 기능 요구 사항에 명시되지 않은 부분은 스스로 판단하여 구현할 수 있습니다.

## 📮 미션 제출 방법

- 미션 구현을 완료한 후 GitHub을 통해 제출해야 합니다.
- **Pull Request로 최종 제출**

## 🚨 과제 제출 전 체크 리스트 - 0점 방지

- 기능을 모두 정상적으로 구현했더라도 **요구 사항에 명시된 출력값 형식을 지키지 않을 경우 0점 처리**됩니다.
- 기능 구현을 완료한 뒤 아래 가이드에 따라 테스트를 실행하고 모든 테스트가 성공하는지 확인합니다.
- **테스트가 실패할 경우 0점 처리**되므로, 반드시 확인 후 제출해야 합니다.

---

### 테스트 실행 가이드

과제에서 구현한 기능이 정상적으로 동작하는지 확인하기 위해 Pytest를 사용하여 테스트를 실행합니다. 아래 단계에 따라 테스트 환경을 설정하고, 모든 테스트가 통과하는지 확인할 수 있습니다.

#### 1. Python 버전 확인

이 프로그램은 Python 3.9 이상에서 동작하도록 개발되었습니다. 먼저 Python 버전이 올바르게 설치되었는지 확인합니다.

```bash
python --version
```

출력 예시:
```bash
Python 3.9.x
```

Python 3.9 이상이 설치되어 있지 않다면, Python 공식 웹사이트([https://www.python.org/](https://www.python.org/))에서 최신 버전을 설치하세요.

#### 2. 의존성 패키지 설치

이 프로그램은 `pytest`를 사용해 테스트를 실행합니다. `requirements.txt` 파일에 명시된 의존성 패키지를 설치합니다. 아래 명령어를 터미널에 입력하여 필요한 패키지를 설치합니다.

```bash
pip install -r requirements.txt
```

#### 3. 테스트 실행

테스트 코드는 `tests/` 디렉터리에 위치해 있습니다. Pytest 명령어를 사용하여 프로젝트의 모든 테스트를 실행할 수 있습니다. 프로젝트 루트 디렉터리에서 아래 명령어를 실행합니다.

```bash
pytest
```

테스트 실행 시 출력 예시는 다음과 같습니다.

```bash
=========================== test session starts ============================
platform darwin -- Python 3.9.x, pytest-6.2.5, py-1.10.0, pluggy-0.13.1
rootdir: /path/to/project
collected 5 items

tests/racingcar/test_application.py .... [ 80%]
tests/study/test_string.py .. [100%]

============================ 100% passing in Xs =============================
```

#### 4. 테스트 결과 확인

테스트가 성공적으로 통과되었다면 위와 같이 **100% passing** 메시지가 출력됩니다. 만약 테스트가 실패하면, 실패한 테스트에 대한 상세한 정보가 제공되며, 이 정보를 바탕으로 문제를 해결할 수 있습니다.

#### 5. 개별 테스트 실행

특정 테스트 파일만 실행하고 싶다면, 해당 파일을 지정하여 Pytest를 실행할 수 있습니다. 예를 들어, `test_application.py` 파일만 테스트하려면 다음 명령어를 실행합니다.

```bash
pytest tests/racingcar/test_application.py
```

#### 6. 커버리지 테스트 (선택 사항)

테스트 커버리지를 확인하고 싶다면, `pytest-cov` 플러그인을 사용할 수 있습니다. 먼저 해당 플러그인을 설치한 후, 아래 명령어로 커버리지를 확인할 수 있습니다.

```bash
pip install pytest-cov
pytest --cov=src
```

---

## 🚀 기능 요구 사항

초간단 자동차 경주 게임을 구현해야 합니다.

- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있습니다.
- 각 자동차에 이름을 부여할 수 있으며, 전진하는 자동차를 출력할 때 자동차 이름도 함께 출력해야 합니다.
- 자동차 이름은 쉼표(,)를 기준으로 구분하며, 이름은 5자 이하만 가능합니다.
- 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 합니다.
- 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후, 무작위 값이 4 이상일 경우입니다.
- 자동차 경주가 끝난 후 누가 우승했는지 알려주어야 하며, 우승자는 한 명 이상일 수 있습니다.
- 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분합니다.
- 사용자가 잘못된 값을 입력할 경우 `ValueError`를 발생시킨 후 프로그램을 종료해야 합니다.

### 입출력 요구 사항

#### 입력

- 경주할 자동차 이름 (쉼표로 구분)

```bash
pobi,woni,jun
```

- 시도할 횟수

```bash
5
```

#### 출력

- 각 차수별 실행 결과

```bash
pobi : --
woni : ----
jun : ---
```

- 단독 우승자 안내 문구

```bash
최종 우승자 : pobi
```

- 공동 우승자 안내 문구

```bash
최종 우승자 : pobi, jun
```

#### 실행 결과 예시

```bash
경주할 자동차 이름을 입력하세요.(이름은 쉼표로 구분)
pobi,woni,jun
시도할 횟수는 몇 회인가요?
5

실행 결과
pobi : -
woni :
jun : -

pobi : --
woni : -
jun : --

pobi : ---
woni : --
jun : ---

pobi : ----
woni : ---
jun : ----

pobi : -----
woni : ----
jun : -----

최종 우승자 : pobi, jun
```

---

## 🎯 프로그래밍 요구 사항

- Python 3.9 이상에서 실행 가능해야 합니다. **정상적으로 동작하지 않을 경우 0점 처리**됩니다.
- 프로그램 실행의 시작점은 `src/racingcar/main.py`의 `main()` 함수입니다.
- 외부 라이브러리는 사용하지 않습니다.
- [Python 코드 스타일 가이드(Python PEP8)](https://peps.python.org/pep-0008/)를 준수하며 프로그래밍합니다.
- 프로그램 종료 시 `sys.exit()`를 호출하지 않습니다.
- 프로그램 구현이 완료되면 `tests/racingcar/test_application.py`의 모든 테스트가 성공해야 합니다. **테스트가 실패할 경우 0점 처리**됩니다.
- 프로그래밍 요구 사항에서 별도로 명시하지 않는 한, 파일과 패키지 이름을 수정하거나 이동하지 않습니다.

### 추가된 요구 사항

- 인덴트(들여쓰기) 깊이를 3이 넘지 않도록 구현합니다. 2까지만 허용합니다.
- 예를 들어, while문 안에 if문이 있으면 들여쓰기는 2입니다.
- 힌트: 함수나 메서드로 분리하면 들여쓰기 깊이를 줄일 수 있습니다.
- 삼항 연산자를 사용하지 않습니다.
- 함수나 메서드가 한 가지 일만 하도록 최대한 작게 만들어야 합니다.
- Pytest와 Assert를 이용해 본인이 정리한 기능 목록이 정상 동작하는지 테스트 코드로 확인합니다.

---

## ✏️ 과제 진행 요구 사항

- 미션은 [python-racingcar](https://github.com/swthewhite/python-racingcar) 저장소를 Fork & Clone하여 시작합니다.
- **기능을 구현하기 전 `docs/README.md`에 구현할 기능 목록을 정리**해 추가합니다.
- **Git의 커밋 단위는 앞 단계에서 `docs/README.md`에 정리한 기능 목록 단위**로 추가합니다.
- [커밋 메시지 컨벤션](https://gist.github.com/stephenparish/9941e89d80e2bc58a153) 가이드를 참고하여 커밋 메시지를 작성합니다.
우승 조건
-경주 끝난 후 우승자 발표, 여러명일 시 쉼표 구분
88 changes: 84 additions & 4 deletions src/racingcar/main.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,90 @@
import random

"""
자동차 경주 프로그램

이 모듈은 간단한 자동차 경주 게임을 구현합니다. 사용자로부터 자동차 이름을 입력받고,
정해진 횟수만큼 자동차가 전진하는 시뮬레이션을 진행하여 최종 우승자를 출력합니다.
"""


def get_car_names():
names = input("경주할 자동차 이름을 입력하세요.(이름은 쉼표로 구분)").split(',')
check_carname(names)
return names
Comment on lines +11 to +14
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

함수 문서화 및 입력값 검증 개선 필요

함수에 대한 docstring이 누락되었으며, 입력값에 대한 추가 검증이 필요합니다.

 def get_car_names():
+    """
+    사용자로부터 자동차 이름을 입력받아 검증하고 반환하는 함수
+    
+    Returns:
+        list: 검증된 자동차 이름 목록
+    Raises:
+        ValueError: 입력값이 유효하지 않은 경우
+    """
     names = input("경주할 자동차 이름을 입력하세요.(이름은 쉼표로 구분)").split(',')
+    names = [name.strip() for name in names]
     check_carname(names)
     return names
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def get_car_names():
names = input("경주할 자동차 이름을 입력하세요.(이름은 쉼표로 구분)").split(',')
check_carname(names)
return names
def get_car_names():
"""
사용자로부터 자동차 이름을 입력받아 검증하고 반환하는 함수
Returns:
list: 검증된 자동차 이름 목록
Raises:
ValueError: 입력값이 유효하지 않은 경우
"""
names = input("경주할 자동차 이름을 입력하세요.(이름은 쉼표로 구분)").split(',')
names = [name.strip() for name in names]
check_carname(names)
return names



def get_try_count():
count = int(input("시도할 횟수는 몇 회인가요?"))
if count == 0:
raise ValueError("시도 횟수는 0보다 커야 합니다.")

Check warning on line 20 in src/racingcar/main.py

View check run for this annotation

Codecov / codecov/patch

src/racingcar/main.py#L20

Added line #L20 was not covered by tests
return count
Comment on lines +17 to +21
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

시도 횟수 검증 개선 필요

다음 사항들을 개선해야 합니다:

  1. 음수 검증 추가
  2. 숫자가 아닌 입력값 처리
  3. 빈 입력 검증
 def get_try_count():
+    """
+    사용자로부터 시도 횟수를 입력받아 검증하는 함수
+    
+    Returns:
+        int: 검증된 시도 횟수
+    Raises:
+        ValueError: 입력값이 유효하지 않은 경우
+    """
-    count = int(input("시도할 횟수는 몇 회인가요?"))
-    if count == 0:
+    try_input = input("시도할 횟수는 몇 회인가요?").strip()
+    if not try_input:
+        raise ValueError("시도 횟수를 입력해주세요.")
+    try:
+        count = int(try_input)
+    except ValueError:
+        raise ValueError("시도 횟수는 숫자여야 합니다.")
+    if count <= 0:
         raise ValueError("시도 횟수는 0보다 커야 합니다.")
     return count
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def get_try_count():
count = int(input("시도할 횟수는 몇 회인가요?"))
if count == 0:
raise ValueError("시도 횟수는 0보다 커야 합니다.")
return count
def get_try_count():
"""
사용자로부터 시도 횟수를 입력받아 검증하는 함수
Returns:
int: 검증된 시도 횟수
Raises:
ValueError: 입력값이 유효하지 않은 경우
"""
try_input = input("시도할 횟수는 몇 회인가요?").strip()
if not try_input:
raise ValueError("시도 횟수를 입력해주세요.")
try:
count = int(try_input)
except ValueError:
raise ValueError("시도 횟수는 숫자여야 합니다.")
if count <= 0:
raise ValueError("시도 횟수는 0보다 커야 합니다.")
return count



def check_carname(x):
"""
자동차 이름을 검증하는 함수.
이름이 5자를 초과할 경우 예외를 발생시킨다.
"""
if not x:
raise ValueError("자동차 이름은 비어있을 수 없습니다.")

Check warning on line 30 in src/racingcar/main.py

View check run for this annotation

Codecov / codecov/patch

src/racingcar/main.py#L30

Added line #L30 was not covered by tests
for i in x:
if not i.strip():
raise ValueError("자동차 이름은 공백일 수 없습니다.")

Check warning on line 33 in src/racingcar/main.py

View check run for this annotation

Codecov / codecov/patch

src/racingcar/main.py#L33

Added line #L33 was not covered by tests
elif len(i) > 5:
raise ValueError("자동차 이름은 5자를 초과할 수 없습니다.")
if len(x) != len(set(x)):
raise ValueError("자동차 이름은 중복될 수 없습니다.")

Check warning on line 37 in src/racingcar/main.py

View check run for this annotation

Codecov / codecov/patch

src/racingcar/main.py#L37

Added line #L37 was not covered by tests
Comment on lines +24 to +37
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

함수명과 매직 넘버 개선이 필요합니다.

  1. 함수명이 snake_case 컨벤션을 따르지 않습니다.
  2. 매직 넘버(5)를 상수로 정의해야 합니다.
+MAX_NAME_LENGTH = 5
+
-def check_carname(x):
+def check_car_name(names):
     """
     자동차 이름을 검증하는 함수.
-    이름이 5자를 초과할 경우 예외를 발생시킨다.
+    
+    Args:
+        names: 검증할 자동차 이름 목록
+    Raises:
+        ValueError: 이름이 비어있거나, 공백이거나, 최대 길이를 초과하거나, 중복된 경우
     """
-    if not x:
+    if not names:
         raise ValueError("자동차 이름은 비어있을 수 없습니다.")
-    for i in x:
-        if not i.strip():
+    for name in names:
+        if not name.strip():
             raise ValueError("자동차 이름은 공백일 수 없습니다.")
-        elif len(i) > 5:
-            raise ValueError("자동차 이름은 5자를 초과할 수 없습니다.")
-    if len(x) != len(set(x)):
+        if len(name) > MAX_NAME_LENGTH:
+            raise ValueError(f"자동차 이름은 {MAX_NAME_LENGTH}자를 초과할 수 없습니다.")
+    if len(names) != len(set(names)):
         raise ValueError("자동차 이름은 중복될 수 없습니다.")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def check_carname(x):
"""
자동차 이름을 검증하는 함수.
이름이 5자를 초과할 경우 예외를 발생시킨다.
"""
if not x:
raise ValueError("자동차 이름은 비어있을 수 없습니다.")
for i in x:
if not i.strip():
raise ValueError("자동차 이름은 공백일 수 없습니다.")
elif len(i) > 5:
raise ValueError("자동차 이름은 5자를 초과할 수 없습니다.")
if len(x) != len(set(x)):
raise ValueError("자동차 이름은 중복될 수 없습니다.")
MAX_NAME_LENGTH = 5
def check_car_name(names):
"""
자동차 이름을 검증하는 함수.
Args:
names: 검증할 자동차 이름 목록
Raises:
ValueError: 이름이 비어있거나, 공백일 없거나, 최대 길이를 초과하거나, 중복된 경우
"""
if not names:
raise ValueError("자동차 이름은 비어있을 수 없습니다.")
for name in names:
if not name.strip():
raise ValueError("자동차 이름은 공백일 수 없습니다.")
if len(name) > MAX_NAME_LENGTH:
raise ValueError(f"자동차 이름은 {MAX_NAME_LENGTH}자를 초과할 수 없습니다.")
if len(names) != len(set(names)):
raise ValueError("자동차 이름은 중복될 수 없습니다.")
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 30-30: src/racingcar/main.py#L30
Added line #L30 was not covered by tests


[warning] 33-33: src/racingcar/main.py#L33
Added line #L33 was not covered by tests


[warning] 37-37: src/racingcar/main.py#L37
Added line #L37 was not covered by tests



def carfoward():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

snake case로 변경하는 것이 좋아보입니다.

"""
자동차를 전진시키는 함수.
0에서 9까지 랜덤값을 생성하여 4 이상이면 1을 반환, 그렇지 않으면 0을 반환한다.
"""
random_number = random.randint(0, 9)
if random_number >= 4:
return 1
return 0
Comment on lines +40 to +48
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

함수명 오타 수정 및 매직 넘버 개선 필요

  1. 함수명에 오타가 있습니다 ('foward' -> 'forward').
  2. 매직 넘버(0, 9, 4)를 상수로 정의해야 합니다.
+MIN_RANDOM = 0
+MAX_RANDOM = 9
+FORWARD_THRESHOLD = 4
+
-def carfoward():
+def car_forward():
     """
     자동차를 전진시키는 함수.
-    0에서 9까지 랜덤값을 생성하여 4 이상이면 1을 반환, 그렇지 않으면 0을 반환한다.
+    
+    Returns:
+        int: 전진 여부 (1: 전진, 0: 정지)
     """
-    random_number = random.randint(0, 9)
-    if random_number >= 4:
+    random_number = random.randint(MIN_RANDOM, MAX_RANDOM)
+    if random_number >= FORWARD_THRESHOLD:
         return 1
     return 0
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def carfoward():
"""
자동차를 전진시키는 함수.
0에서 9까지 랜덤값을 생성하여 4 이상이면 1 반환, 그렇지 않으면 0 반환한다.
"""
random_number = random.randint(0, 9)
if random_number >= 4:
return 1
return 0
MIN_RANDOM = 0
MAX_RANDOM = 9
FORWARD_THRESHOLD = 4
def car_forward():
"""
자동차를 전진시키는 함수.
Returns:
int: 전진 여부 (1: 전진, 0: 정지)
"""
random_number = random.randint(MIN_RANDOM, MAX_RANDOM)
if random_number >= FORWARD_THRESHOLD:
return 1
return 0



def status(cars):
"""
각 자동차의 진행 상태를 출력하는 함수.
각 자동차의 진행 상태를 '-'로 나타낸다.
"""
for key, value in cars.items():
print(f"{key} : {value * '-'}")


def winner(cars):
"""
최종 우승자를 출력하는 함수.
가장 많은 전진을 한 자동차들을 우승자로 표시한다.
"""
maxfoward = max(cars.values())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

snake case로 변경하는 것이 좋아보입니다.

winners = [key for key, value in cars.items() if value == maxfoward]
print(f"\n최종 우승자 : {', '.join(winners)}")


def main():
"""
프로그램의 진입점 함수.
여기에서 전체 프로그램 로직을 시작합니다.
프로그램의 메인 함수.
자동차 이름과 시도할 횟수를 입력받고, 경주를 진행한 후 우승자를 출력한다.
"""
# 프로그램의 메인 로직을 여기에 구현
print("프로그램이 시작되었습니다.")
try:
car_names = get_car_names()
cars = dict.fromkeys(car_names, 0)
try_count = get_try_count()
for _ in range(try_count):
for name in car_names:
cars[name] += carfoward()
status(cars)
print()
winner(cars)
except ValueError as e:
print(f"오류: {str(e)}")
raise


if __name__ == "__main__":
Expand Down
2 changes: 1 addition & 1 deletion tests/racingcar/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def test_전진_및_정지(capsys):
main() # 프로그램 실행

# 출력값을 캡처한 후 검증
캡처된_출력 = capsys.readouterr()
캡처된_출력 = capsys.readouterr().out
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ㅎㅎ 제가 실수한 부분인데 잘 체크하고 고치셨어요!

assert all(예상_출력 in 캡처된_출력 for 예상_출력 in ["pobi : -", "woni : ", "최종 우승자 : pobi"])


Expand Down
Loading