알고리즘

_Opacity·2021년 12월 21일

프로그래밍

목록 보기
9/9
post-thumbnail

하루 간 진행되는 프로젝트, 알고리즘에 대해 스스로의 코드와 튜터님의 코드를 비교하며 코드리뷰를 해보겠다.

1. 업, 다운 게임

PC가 랜덤으로 수를 정하고 이를 5번 이내에 사용자가 맞춰나가는 게임이다.

내가 썼던 코드는 다음과 같다.

import random

c = random.randrange(1, 100)
print(c)

try:
    for i in range(1, 6):
        p = int(input('값을 입력하세요 : '))
        if i < 5:
            if p < c:
                print('업')
                continue
            elif p == c:
                print('정답')
                break
            else:
                print('다운')
                continue
        else:
            if p == c:
                print('정답')
            else:
                print('탈락')

except:
    print('에러')
    

다소 if~ else가 많은 것 같아 좋은 코드는 아니라고 생각하지만 무언가 생각해내고 만든 코드였던지라 처음엔 만족스러웠다. 지금 생각해보니 입력 값을 받고 굳이 i로 한 번 더 확인해줄 필요성이 없다는 것이 보인다. 조금 수정해보자.

from random import randint

c = randint(1, 100)
print(c)
clear = 0

for i in range(0, 5):
    p = int(input('값을 입력하세요 : '))
    if p > c:
        print('다운')
    elif p < c:
        print('업')
    else:
        clear = 1
        break

if clear == 1:
    print('성공')
else:
    print('실패 ! 정답은 {0}였습니다.'.format(c))

확실히 코드가 한 눈에 들어온다. 전과 다른 것은 반복문 내에서 성공의 여부를 알려주는 것이 아닌 clear라는 변수에 그 의미를 담고 이 변수가 1이 되면 성공인 것을, 아니면 실패인 것을 표시해준다.

튜터님 답안

from random import randrange

#1. 컴퓨터가 1~100 중에서 임의의 수(C)를 랜덤으로 정한다. (random 패키지에 있는 randrange 함수를 사용할 것)
c = randrange(1, 101)

success = 0

#4. 2\~3번을 4번 더 반복한다. (즉, 2\~3번을 for문으로 작성할 것)
for i in range(0, 5):
    #2. 플레이어가 input 함수를 통해서 임의의 수(P)를 입력한다.
    #3. 컴퓨터는 C와 P를 비교하여, 업 / 다운 중 하나를 출력한다. (비교할 때는 if문을 사용할 것, '업' 혹은 '다운'은 print 문을 통해서 출력할 것)
    p = int(input())
    if c > p:
        print('업')
    elif c < p:
        print('다운')
    else:
        success = 1
        break

#5. 플레이어가 총 5회 안에 컴퓨터가 정한 수를 맞히면 '성공', 맞히지 못하면 '실패' 를 출력한다.
if success == 1:
    print('성공')
else:
    print('실패')

내가 수정했던 답안과 유사하다. 이 코드를 참고했기 때문에 윗 코드와 비슷하게 나왔다고 생각한다.

2. 업, 다운 게임 - 인공지능 버젼

이번엔 반대로 사용자가 100까지의 수를 정하고 이를 컴퓨터가 '업', '다운' 을 통해 맞춰가는 것이다.

내가 썼던 코드를 보자.

p = int(input('값을 입력하세요: '))
c = 100

if p > 100:
    print('100 이상의 값이 입력되었습니다')

else:
    c = int(c / 2)
    print('{0} 값이 예측되었습니다.'.format(c))
    if p == c:
        print('정답')
    else:
        while 1:
            question = input('업? 다운? ')
            if question == '업':
                if p < c:
                    print('에러')
                    continue
                else:
                    c = int((p + c) / 2) + 1
                    print('{0} 값이 예측되었습니다.'.format(c))
                    if p == c:
                        print('정답')
                        break
                    if p > c:
                        continue

            if question == '다운':
                if p > c:
                    print('에러')
                    continue
                else:
                    c = int(c / 2) + 1
                    print('{0} 값이 예측되었습니다.'.format(c))
                    if p == c:
                        print('정답')
                        break
                    if p < c:
                        continue

여기서 고민했던 것은 컴퓨터가 어떤 수로 예측하는 것이 좋나였다. 이 코드는 50을 기준으로 둔 코드여서 '업'일 경우에는 '업'을 계속하게 되는 좋지 않은 코드였다고 생각한다. 다시 한 번 생각해보면서 코드를 짜보자.

직접 짜보려고 했으나 어려워서 튜터님을 코드를 참고하면서 코드리뷰했다.

from math import floor # 내림

number_list = range(1, 101)

p = int(input('값을 입력하세요 : '))
while p < 1 or p > 101:
    print('다시 입력하세요.')
    p = int(input('값을 입력하세요 : '))

while True:
    c = number_list[floor(len(number_list) / 2)]
    print('{0}?'.format(c))
    if c == p:
        print('성공')
        break

    question = input('업, 다운? : ')
    if question == '업' and p > c:
        number_list = number_list[number_list.index(c):]
    elif question == '다운' and p < c:
        number_list = number_list[:number_list.index(c)]
    else:
        print('당신은 거짓말을 쳤습니다.')

대략적인 나의 생각인 위와 같다. 튜터님은 리스트 형식으로 값을 슬라이싱하여 남은 리스트 항목의 길이에서 반 부분에 해당하는 인덱스를 가져와 이를 비교하고 아니라면 다시 업, 다운? input을 받는 식인 것 같다.

같이 공부하는 다른 튜티님들의 코드도 살펴보자.

lilNum = 0
bigNum = 100
num = int(input('숫자를 입력하세요'))
answer = ""
a = num
# 입력되는input값을 숫자로 만들어준다
while True:
    half = int((lilNum+bigNum)/2)
    print('제생각에는...'+ str(half))
    if (half == num):
        print("정답입니다")
        break
        # 정답일때 출력값 break문으로 반복종료
     # 사용자가 거짓을 말할때 실행할 코드를 생각중입니다.

    answer =input("up? down?")
    if (answer == "up" and half > a):
         print("Don't be a liar :(")
    elif (answer == "down" and half < a):
         print("Don't be a liar :(")
    elif (answer == "up"):
            lilNum=half
    elif (answer == "down"):
            bigNum = half

튜티님의 코드 중 불필요한 코드가 있길래 삭제한 후 실행해보았다. 일단 지운 코드는 다음과 같다.

lilNum = 0
bigNum = 100
num = int(input('숫자를 입력하세요'))
answer = ""

while True:
    half = int((lilNum+bigNum)/2)
    print('제생각에는...'+ str(half))
    if (half == num):
        print("정답입니다")
        break

    answer =input("up? down?")
    if (answer == "up" and half > num):
         print("Don't be a liar :(")
    elif (answer == "down" and half < num):
         print("Don't be a liar :(")
    elif (answer == "up"):
            lilNum= half
    elif (answer == "down"):
            bigNum= half

튜터님과 튜티님의 코드 실행에는 별 차이가 없지만 알고리즘을 공부하면서 느낀 것이 있다. 바로, 어떻게 하면 최적의 코드로 동일한 실행을 할 수 있는가의 차이였다. 방금 윗 코드들도 실행은 문제가 없을지언정 실행되는 횟수가 다름을 알 수 있다. 이처럼 앞으로 개발을 해낼 때 있어서 어떻게 하면 최대한 간결하게 짤 수 있을지 고민을 할 줄 아는 그런 개발자로 성장해야겠음을 느끼게 되었다.

마지막으로 이 문제를 한 번 더 코딩해보고 다음 문제로 넘어가자.

3. 31게임

컴퓨터와 사용자가 대결을 한다. 총 3개의 숫자를 말할 수 있고 먼저 31을 말하면 지는 게임이다.

내가 사용했던 소스코드는 다음과 같다.

from random import randint

numbers_list = list(range(1, 32))
who_first = randint(1, 2)
win = 0

def computer_Say():
    index = randint(1, 3)
    print('컴퓨터의 차례입니다 : {0}'.format(index))
    computer_list = numbers_list[: index]
    del numbers_list[: index]
    print(computer_list)

def user_Say():
    num = int(input('당신의 차례입니다 : '))
    user_list = numbers_list[: num]
    if len(user_list) < 4:
        del numbers_list[: num]
        print(user_list)
    else:
        print('잘못된 값을 입력하였으므로 컴퓨터의 차례로 변경됩니다.')

while True:
    if who_first == 1:
        if numbers_list != [ ]:
            user_Say()
            win = 1
            if numbers_list == [ ]:
                break
            else:
                computer_Say()
                win = 0
                continue
        else:
            break
    elif who_first == 2:
        if numbers_list != [ ]:
            computer_Say()
            win = 0
            if numbers_list == [ ]:
                break
            else:
                user_Say()
                win = 1
                continue
        else:
            break
    else:
        print('에러')
        break

if win == 0:
    print('컴퓨터 패배')
if win == 1:
    print('유저 패배')

이 소스코드는 1부터 31까지 리스트로 받고 컴퓨터는 랜덤의 수를 받고 사용자는 입력한 수를 받아 그만큼 리스트에서 제거하는 방식이다. 이제 튜터님의 코드를 참고하면서 복습해보자.

from random import randint

last_number = 0
who_first = randint(0, 2)

def computer_say():
    c = randint(1, 3)
    for i in range(1, 1 + c):
        print('컴퓨터 : {0} '.format(last_number + i))
    return last_number + i

def user_say():
    p = int(input('숫자를 입력하세요. '))
    for i in range(1, 1 + p):
        print('{0}을 입력하셨습니다. '.format(last_number + i))
    return last_number + i

if who_first == 1:
    last_number = user_say()

while True:
    last_number = computer_say()
    if last_number == 31:
        print('유저의 승리!')
        break
    last_number = user_say()
    if last_number == 31:
        print('컴퓨터의 승리!')
        break

여기까지 코딩해보면서 아직 내가 반복문의 부분을 헷갈려하고 있다는 사실도 알게 된 것 같다. 지금으로써는 더 많은 반복문을 접해보며 불필요한 부분을 줄여나가는 것이 가장 큰 것이라고 생각한다.

4. 31게임 - 인공지능 버젼

위와 비슷하지만 컴퓨터가 무조건 이기는 31게임을 만들어보자. 단, 한 가지의 경우는 유저가 이길 수 있다.

이 부분은 사실 못만들었다. 그래서 사용하신 코드를 보면서 천천히 분석해보자.

from random import randint, randrange

last_number = 0

def computer_say():
    if (last_number + 3 + 2) % 4 == 0:
        c = 3
    elif (last_number + 2 + 2) % 4 == 0:
        c = 2
    elif (last_number + 1 + 2) % 4 == 0:
        c = 1
    else:
        c = randrange(1, 4)

    for i in range(1, c + 1):
        print('컴퓨터 : {0}'.format(last_number + i))
    return last_number + c

def user_say():
    user = int(input('사용자 : '))
    while user not in [1, 2, 3]:
        print('다시 입력하세요!')
        user = int(input('사용자 : '))
    for i in range(1, user + 1):
        print('사용자 : {0}'.format(last_number + i))
    return last_number + user

who_first = randint(0, 1)
if who_first == 1:
    last_number = computer_say()

while True:
    last_number = user_say()
    if last_number == 31:
        print('컴퓨터의 승리')
        break
    last_number = computer_say()
    if last_number == 31:
        print('유저의 승리')
        break

5. 게임 - 클래스 이용한 게임

턴제 RPG게임을 만들어보자.

처음에는 무엇을 해야할지 가늠을 못잡았다. 클래스에 대한 기초적인 지식만 가지고 있었기에 열심히 구글링을 하며 검색했던 것 같다. 내가 구현한 코드는 다음과 같다.

from random import randint

class Object:
    def __init__(self, name, hp, damage):
        self.name = name
        self.hp = hp
        self.damage = damage

    # 기본 공격 함수
    def attack(self, target):
        target.hp -= 10
        print('{0}(이)가 {1}(을)를 공격했습니다.'.format(self.name, target.name))

class Player(Object):
    def __init__(self, name, hp, damage):
        Object.__init__(self, name, hp, damage)

    # 마법 공격 함수
    def magicattack(self, target):
        target.hp -= 50
        print('{0}(이)가 {1}(을)를 마법으로 공격했습니다.'.format(self.name, target.name))
        print('{0}의 남은 체력은 {1}입니다.'.format(target.name, target.hp))
        return target.hp

    # 플레이어 정보 확인 함수
    def show_info(self):
        print('{0}의 현재 체력은 {1}입니다.'.format(user.name, user.hp))

    # 플레이어 턴 함수
    def player_turn(self):
        select = input('공격? 마법? : ')
        monsterSelect = input('대상은 누구에게 하시겠습니까? : ')

class Monster(Object):
    def __init__(self, name, hp, damage):
        Object.__init__(self, name, hp, damage)

    # 몬스터 대기 함수
    def stop(self):
        print('{0}(이)가 대기했습니다.'.format(self.name))

    # 몬스터 힐 함수
    def heal(self):
        self.hp += 10
        print('{0}(이)가 자신의 체력을 10만큼 회복했습니다.'.format(self.name))
        return self.hp

    # 몬스터 정보 확인 함수
    def show_info(self):
        for monster in monster_list:
            if monster.hp != 0:
                print('{0}의 현재 체력은 {1}입니다.'.format(monster.name, monster.hp))
        # if monster1.hp != 0:
        #     print('{0}의 현재 체력은 {1}입니다.'.format(monster1.name, monster1.hp))
        # if monster2.hp != 0:
        #     print('{0}의 현재 체력은 {1}입니다.'.format(monster2.name, monster2.hp))
        # if monster3.hp != 0:
        #     print('{0}의 현재 체력은 {1}입니다.'.format(monster3.name, monster3.hp))

    # 몬스터 사망 여부 함수
    def monster_is(self):
        while True:
            for i in range(len(monster_list)):
                if monster_list[i].hp <= 0:
                    monster_list.pop(i)
                    break
                if len(monster_list) == 0:
                    print('승리')
                    break
                else:
                    continue

    # 몬스터 턴 함수
    def monster_turn(self):
        # 몬스터 치료
        if computerSelect == 1:
            print('몬스터가 치료합니다.')

        # 몬스터 대기
        if computerSelect == 2:
            print('몬스터가 대기합니다.')

        # 몬스터 공격
        if computerSelect == 3:
            print('몬스터가 공격합니다.')


computerSelect = randint(1, 3)
user = Player('전사', 100, 10)
monster1 = Monster('미니 고블린', 10, 10)
monster2 = Monster('고블린', 30, 30)
monster3 = Monster('슈퍼 고블린', 50, 50)
monster_list = [monster1, monster2, monster3]

어... 개인적으로 답을 보았을 때 기초적인 실수를 찾아버렸다. 몬스터 정보 확인 함수 등... 클래스에 들어가지 않아야 하는데 들어가버렸다... 하하 튜터님 코드를 보면서 분석을 해보자.

class Object:
    def __init__(self, name, hp, power):
        self.name = name
        self.hp = hp
        self.power = power
    def attack(self, target):
        print(f"{self.name}이(가) {target.name}을(를) 공격!")
        print(f"{target.name}에게 {self.power}만큼의 데미지!")
        target.hp = target.hp - self.power
        if (target.hp <= 0):
            print(f"{target.name}을(를) 죽였습니다!")
        else:
            print(f"{target.name}의 HP가 {target.hp}이 되었습니다")

먼저 Object 클래스부터 보자. 어떻게 분석했는지는 위와 같이 따로 생각하면서 기재하겠다.

이 부분에서 가장 중요한 것은 인자값에 해당하는 상세 정보(name, hp, power)를 불러올 수 있다는 것이다. 처음에는 몰라서 별의 별 방법을 다 쓴 것 같다.

class Player(Object):
    def magic(self, target):
        print(f"{self.name}이(가) {target.name}에게 마법을 사용!")
        print(f"{target.name}에게 50만큼의 데미지!")
        target.hp = target.hp - 50
        if (target.hp <= 0):
            print(f"{target.name}을(를) 죽였습니다!")
        else:
            print(f"{target.name}의 HP가 {target.hp}이 되었습니다")

class Monster(Object):
    def cure(self):
        self.hp = self.hp + 10
        print(f"{self.name}이 체력을 10 회복했습니다!")
    def stay(self):
        print(f"{self.name}이 대기했습니다!")

클래스는 이 정도로 구성한다. 나는 이 부분까지 클래스는 각 객체가 가지고 있는 기본적인 특성이라고 생각한다. 이제 실제로 값을 입력받아서 출력해보자. 그 전에 추가해야할 함수 부분에 대해서 보자.

from random import choice
from time import sleep # sleep 은 게임을 진행할 때 텍스트를 보기 편하게 하기 위함.

def createobjects():
    Warrior = Player('전사', 100, 10)
    # 이름으로 해당 몬스터 인스턴스를 찾을 수 있도록, 딕셔너리 형태로 Monsters 를 묶어놓음
    Monsters = {}
    Monsters['미니고블린'] = Monster('미니고블린', 10, 10)
    Monsters['고블린'] = Monster('고블린', 30, 30)
    Monsters['슈퍼고블린'] = Monster('슈퍼고블린', 50, 50)

설명에서도 적었지만 딕셔너리 형태로 인자값을 받아 묶어 놓는 것은 최선의 방법인 것 같다.

def showinfo(Player, Monsters):
    print("\n--------------턴 시작---------------")
    print(f"{Player.name}의 체력 : {Player.hp}")
    for key, value in Monsters.items():
        print(f"{value.name}의 체력 : {value.hp}")

def playerturn(Player, Monsters):
    print("\n--------------플레이어 턴--------------")
    # 예외처리는 각자 해보시는 것을 추천드리겠습니다!
    # 예외처리는 try except 를 쓰는 것이 정확한 방법이긴 합니다!
    command = input('공격? 마법? : ')
    target = input('누구를 공격? : ')
    if command == '공격':
        Player.attack(Monsters[target])
    elif command == '마법':
        Player.magic(Monsters[target])
    return Monsters

이제 다음 차례를 생각해보자. 지금까지 턴을 시작하기 전 정보를 알려주는 함수, 유저의 턴에 대한 함수를 구현했는데 이 다음으로 무엇이 오면 좋을까? 유저가 데미지를 입히는 함수를 구현했기 때문에 그에 대한 몬스터의 체력이 깍여 죽은 몬스터가 있는지 확인하는 작업을 거친 후 몬스터 턴을 거치면 될 것이다.

def check_mdead(Monsters):
    # 이번 턴에서 죽은 몬스터가 있는지 확인
    dead_monsters = []
    for key, value in Monsters.items():
        if value.hp <= 0:
            dead_monsters.append(key)
    # 죽은 몬스터는 몬스터 명단에서 삭제
    for i in dead_monsters:
        del Monsters[i]
    # 남은 몬스터가 없다면 승리 출력, 있다면 몬스터 그대로 리턴해주기
    if len(Monsters) <= 0:
        return Monsters, True
    else:
        return Monsters, False

def monsterturn(Player, Monsters):
    print("\n------------몬스터 턴-----------")
    sleep(3)
    for key, value in Monsters.items():
        commands = ['cure', 'attack', 'stay']
        command = choice(commands)
        if command == 'cure':
            value.cure()
        elif command == 'attack':
            value.attack(Player)
        elif command == 'stay':
            value.stay()
    return Player

마지막으로 유저가 죽었을 때 게임이 종료되는 부분만 넣어주면 된다.

def check_pdead(Player):
    if Player.hp <= 0:
        return True
    else:
        return False

이제 이 게임을 실행시켜보자.

while True:
    showinfo(Warrior, Monsters)
    Monsters = playerturn(Warrior, Monsters)
    sleep(1)
    Monsters, ismdead = check_mdead(Monsters)
    if ismdead:
        print('\n승리!!!')
        break
    Warrior = monsterturn(Warrior, Monsters)
    ispdead = check_pdead(Warrior)
    if ispdead:
        print("\n패배!!!")
        break
    sleep(1)

지금까지 알고리즘 문제를 풀어보았다. 문제를 풀어보면서 답이 무엇이냐가 중요한 것보다 다양한 풀이를 보면서 그에 따른 해결방법도 여러가지가 있다는 것을 알 수 있었고 나는 아직 초보자 단계이기 때문에 if, else문에 의존하고 있는 것도 느꼈다. 이를 최소화하고 클린한 코드를 만드는 방법에 대해 조금 더 연구하고 많은 문제를 접해봐야 할 것 같다.

지금까지 사용한 소스 코드

profile
열심히 개발하려고 하는 주니어 개발자-!

0개의 댓글