파이썬 실전 프로젝트

숫자 맞추기 게임 2 (스트라이크 볼 게임)

토픽 파이썬 실전 프로젝트

이번에는 숫자 야구라는 간단한 게임을 만들어 보겠습니다.

앞에서 만든 숫자 맞추기 게임을 조금만 수정하면 됩니다.

게임의 규칙은 사용자가 값을 입력해서, 정답과 자리가 정확하게 같으면 스트라이크, 자리는 다르지만 숫자가 존재하기만 하면 볼이라고 알려주는 규칙입니다.

정답이 195일때

123 을 입력하면, 1 스트라이크

193 을 입력하면, 2 스트라이크

951 은 3볼,

159 는 1스트라이크 2볼입니다.

 

 

1. 앞서 만든 코드 가져오기

기본적인 구조는 이전 코드와 비슷합니다.

import random
number = random.randint(1,999)
print(number)

while True:
    try:
        guess = int(input('숫자를 입력하세요 :'))
        if guess == number:
            print('정답입니다')
            break
        elif guess > number:
            print('더 작은 수 입니다')
        else:
            print('더 큰 수 입니다.')
    except:
        print('1-999중의 숫자를 입력하세요')

 

2. 숫자를 문자로 바꾸고, 자릿수는 0으로 채우기.

이전의 숫자맞추기 게임은 크기를 비교해야해서 숫자가 편했지만, 이번에는 각 자릿수를 비교해야 하므로, 문자가 비교하기 편합니다. 그래서 생성된 난수는 문자로 바꿔주고, 사용자 입력은 원래 문자타입이므로 숫자로 변환하지 않고 그대로 두겠습니다.

import random
number = str(random.randint(1,999)).zfill(3) # 문자로 바꿔주고, 0으로 채움.
print(number)

 zfill() 함수는 사용자가 3자리를 입력하지 않았을 경우를 대비해서, 문자열 왼쪽의 빈칸을 0으로 채우라는 명령입니다.

 

루프문은, 정답인지 확인하는 부분만 남기고 삭제해주겠습니다. try : except : 구문도 제거해주세요.

while True:
    guess = input('숫자를 입력하세요 :')  #기본타입이 문자이기 때문에, 별도로 바꿀필요가 없습니다.
    if guess == number:
        print('정답입니다')
        break
    else:
        print('틀렸습니다.')
 
3. 스트라이크, 볼 판단

스트라이크 볼을 판단하는 원리는, 자리가 완전히 일치할경우 strike 변수에 1증가, 자릿수가 달라도 숫자가 존재하면 ball 변수에 1증가 시키도록 했습니다.

import random
number = str(random.randint(1,999)).zfill(3)
print(number)

while True:
    guess = input('숫자를 입력하세요 :')
    if guess == number:
        print('정답입니다')
        break
    else:
        strike = 0
        ball = 0
        for i in range(3):
            if guess[i] == number[i]:
                strike += 1
            elif guess[i] in number:
                ball+=1
        print("스트라이크:{} 볼:{}".format(strike,ball))
195
숫자를 입력하세요 :190
스트라이크:2 볼:0
숫자를 입력하세요 :295
스트라이크:2 볼:0
숫자를 입력하세요 :951
스트라이크:0 볼:3
숫자를 입력하세요 :159
스트라이크:1 볼:2
숫자를 입력하세요 :195
정답입니다

 

4. 중복처리

위 코드에 한가지 문제가 있는데, 중복된 숫자가 있을경우, 정확하지 않은 결과가 나옵니다.

예를들어, 정답이 122일때 113을 입력하면 원래는 1스트라이크만 나와야 하지만, 현재 코드는 1스트라이크, 1볼이 나옵니다.(1이 두번 검사됩니다) 그래서 한번 스트라이크나 볼로 판단된 자리는 별도로 표시를 해줘서, 재검사가 되지 않도록 해야합니다. (아니면, 처음부터 중복되지 않은 정답을 만들어낼수도 있습니다. 다음토픽 참조)

이를 위해서, 임시 변수 number2(리스트타입) 를 만들어서 정답 122를 복사한 다음에( ['1','2','2'] ), number2 와 guess 를 비교해줍니다. 스트라이크나 볼판단시에, 's'와 'b'등의 숫자가 아닌 임의의 문자로 변경을 해주면, 다음번 if문에서 그자리는 무조건 false로 판단되기 때문에, 중복을 피할수 있습니다.

import random
number = str(random.randint(1,999)).zfill(3)
print(number)

while True:
    guess = input('숫자를 입력하세요 :')
    if guess == number:
        print('정답입니다')
        break

    strike = 0
    ball = 0
    number2 = list(number)  # 임시 변수에 값 저장. 
    for i in range(3):
        if guess[i] == number2[i]:  # number 대신 number2와 비교
            strike += 1
            number2[i] = 's'  # 스트라이크일경우, 해당숫자를 's'로 바꿔줌.

    for i in range(3):
        if guess[i] in number2:
            ball += 1
            number2[number2.index(guess[i])] = 'b' # 위치를 모르기 때문에, 인덱스를 찾아서, 값을 바꾸어 줍니다.
    print(strike,ball, number2)

416
숫자를 입력하세요 :444
1 0 ['s', '1', '6']
숫자를 입력하세요 :145
0 2 ['b', 'b', '6']
숫자를 입력하세요 :146
1 2 ['b', 'b', 's']
숫자를 입력하세요 :416
정답입니다

 

5. 잘못된 입력 처리

이전 토픽 숫자맞추기에는 숫자가 아니기만 하면 다 예외처리를 해주면 됐지만, 이번에는 숫자 자리에도 제한이 있기때문에, 좀더 세밀하게 예외처리를 해줘야 합니다.

그래서 try: except: 구문에서 좀더 구체적인 조건을 검사해줘야 합니다.

while True:
    try:    # 에러 검사
        if .... :
            raise   # 강제로 에러를 발생시켜서, except 문으로 넘어갑니다. 
    except:
        print('에러메세지')
    else:
        # 에러 없을때 실행할 코드

기본적인 구조는 위와 같습니다. 이전토픽에서는 try 구문에서 모든 코드를 실행하고, 예외(오류가 발생)시에만 except 구문으로 갔는데, 이번에는 try문에서 에러가 날때만 except 문으로 가는것이 아니라, 특정 조건을 검사해서 조건에 맞으면 혹은 맞이 않으면 강제로 에러를 발생시켜서 except 문으로 넘어가게 합니다. 이렇게 하는 이유는 숫자자릿수나 길이같은것은 현재 코드에서는 에러가 자동으로 생기지는 않지만 어쨌든 별도로 처리를 해야해서 if문으로 미리 검사해준다음 원하는 길이가 아닐경우, raise 문으로 강제로 에러를 발생시켜서 except으로 넘어가서 됩니다.

import random
number = str(random.randint(1,999)).zfill(3)
print(number)

while True:
    guess = input('숫자를 입력하세요 :')
    
    try:
        if len(guess) != 3:   # 입력이 세자리가 아니면
            raise     # 에러를 발생시킵니다.

    except :
        print('0~999 사이의 숫자를 입력해주세요')  # 에러메세지

    else:                # 에러가 없을때의 정상적인 코드
        if guess == number:
            print('정답입니다')
            break

        strike = 0
        ball = 0
        number2 = list(number)
        for i in range(3):
            if guess[i] == number2[i]:
                strike += 1
                number2[i] = 's'

        for i in range(3):
            if guess[i] in number2:
                ball += 1
                number2[number2.index(guess[i])] = 'b'
        print(strike,ball)

 

숫자가 아닌 입력을 걸러낼려면, try 문에 조건문을 더 추가해주면 됩니다.

    try:
        if len(guess) != 3:
            raise

        for i in guess:             # 모든 입력이 숫자인지 검사
            if i not in '0123456789':  
                raise 

 

댓글

댓글 본문
  1. nomadlife
    아래 참고해보세요. (for, while, if 문들은 다음줄 들여쓰기 해주세요(붙어있는줄 모두). ㅎㅎ 댓글에 공백이 없어져버리네요.)

    1. 숫자를 정해놓고 뽑아쓰기. pop
    import random

    num = list(range(1,10))

    numbers = []
    for i in range(3):
    numbers.append(num.pop(num.index(random.choice(num))))

    print(numbers)

    2. 새로운 수를 추가할때마다 중복검사하기
    import random

    numbers = []

    while len(numbers)<3:
    num = random.randint(1,9)
    if num not in numbers:
    numbers.append(num)

    print(numbers)


    3. set(집합)을 이용하면 중복검사를 할필요 없어요. (중복은 자동으로 무시됩니다.)
    import random

    number = set()
    while len(number)<3:
    number.add(random.randint(1,9))

    print(number)


    4. 아니면 고등학교수학 콤비네이션을 이용할수도 있습니다.
    import random
    import itertools

    numbers = list(itertools.combinations('123456789',3))
    number = random.choice(numbers)

    print(number)

    이거말고도 더 있을수 있으니, 다양하게 시도해보세요.
    대화보기
    • 이제동
      근데 제가 아는 숫자야구는 처음 3자리 숫자가 서로 다른 걸로 알고 있어서 랜덤하게 각각 다른 3자리 수를 만들려고 해봤는데요.

      import random

      num = [1,2,3,4,5,6,7,8,9]

      tmp1 = (random.randint(0,8))
      num1 = str(num.pop(tmp1))

      tmp2 = (random.randint(0,7))
      num2 = str(num.pop(tmp2))

      tmp3 = (random.randint(0,6))
      num3 = str(num.pop(tmp3))

      number = [num1, num2, num3]

      근데 제가 정말 배운지 얼마 안 된 초보라
      뭔가 너무 길게 짠 것 같은데 혹시 짧게 만들 방법 없을까요?
    • 쿨피
      네 맞습니다.
      초보라 어렵게 짠듯요..-.-;;
      대화보기
      • nomadlife
        아 코드 감사합니다. 살짝 어렵네요 ^^; 초보가 아니신거 같은데요? 100, -50은 스트라이크/볼을 양수음수로 구분하실려고 넣으신건가요?
        대화보기
        • 쿨피
          허접하지만 아래 처럼 해보니 중복처리 되네요 아직 초짜라서 ^^
          for j in [0,1,2]:
          TempRST = 0
          for k in [0, 1, 2]:

          if j == k and random_num[j] == guess[k]:
          Result[k][j] = 100
          elif j != k and random_num[j] == guess[k]:
          Result[k][j] = -50
          else:
          Result[k][j] = 0
          TempRST = TempRST+Result[k][j]

          print(TempRST)
          if TempRST > 0:
          STK = STK + 1
          elif TempRST < 0:
          BALL = BALL + 1
          print(Result)
          print("%d strikes and %d Balls " % (STK, BALL))
          대화보기
          • nomadlife
            제가 중복처리를 안해줬네요 뭔가 깔끔한 코드가 생각이 안서서, 그냥 저대로 뒀던듯 합니다. ^^; 혹시 아이디어 있으시면 공유 부탁드릴게요. 저도 고민해보겠습니다.
            대화보기
            • 쿨피
              number 198 guess 181 인 경우 1S1B 이어야 맞을 듯 한데 위 로직으로는 1S2B 이 나오네요,