하루 간 진행되는 프로젝트, 알고리즘에 대해 스스로의 코드와 튜터님의 코드를 비교하며 코드리뷰를 해보겠다.
내가 썼던 코드는 다음과 같다.
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('실패')
내가 수정했던 답안과 유사하다. 이 코드를 참고했기 때문에 윗 코드와 비슷하게 나왔다고 생각한다.
내가 썼던 코드를 보자.
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
튜터님과 튜티님의 코드 실행에는 별 차이가 없지만 알고리즘을 공부하면서 느낀 것이 있다. 바로, 어떻게 하면 최적의 코드로 동일한 실행을 할 수 있는가의 차이였다. 방금 윗 코드들도 실행은 문제가 없을지언정 실행되는 횟수가 다름을 알 수 있다. 이처럼 앞으로 개발을 해낼 때 있어서 어떻게 하면 최대한 간결하게 짤 수 있을지 고민을 할 줄 아는 그런 개발자로 성장해야겠음을 느끼게 되었다.
마지막으로 이 문제를 한 번 더 코딩해보고 다음 문제로 넘어가자.
내가 사용했던 소스코드는 다음과 같다.
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
여기까지 코딩해보면서 아직 내가 반복문의 부분을 헷갈려하고 있다는 사실도 알게 된 것 같다. 지금으로써는 더 많은 반복문을 접해보며 불필요한 부분을 줄여나가는 것이 가장 큰 것이라고 생각한다.
이 부분은 사실 못만들었다. 그래서 사용하신 코드를 보면서 천천히 분석해보자.
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
처음에는 무엇을 해야할지 가늠을 못잡았다. 클래스에 대한 기초적인 지식만 가지고 있었기에 열심히 구글링을 하며 검색했던 것 같다. 내가 구현한 코드는 다음과 같다.
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문에 의존하고 있는 것도 느꼈다. 이를 최소화하고 클린한 코드를 만드는 방법에 대해 조금 더 연구하고 많은 문제를 접해봐야 할 것 같다.