[Python] practice 1. 포션 종류 및 효과 추가

 

RPG 게임에는 포션이 꼭 필요하다. 그래서 처음 자바로 만들 때 계획은 회복약을 다양하게 만들어서 리스트를 만든 다음에 캐릭터가 선택해서 적용되게 하는 것이었다. 하지만 처음 시도한 미니 프로젝트다보니 코드를 작성하는게 너무 어려웠고, 결국 포션의 종류는 하나로 한정시킬 수 밖에 없었다. 그래도 단순하게 작성했기 때문에 당시에 큰 문제 없이 코드를 만들었다.

 

하지만 파이썬으로 옮기는 작업을 하면서 욕심이 생기기 시작했고 포션 종류를 하나 더 추가하기로 했다. 그렇게 하자마자 문제가 발생했다. 단 한 종류를 더 추가했을 뿐인데 어째서! 라는 생각이 들면서 그냥 원래대로 할까 하는 마음이 생겼지만 그렇게 하면 발전이 없다는 생각에 될 때까지 해보기로 했다.


포션을 사용했는데 수량이 변하지 않는 문제부터 해결해야 했다.

 

class RegularPotion(Potion, ConsumableItem, DropItem):
    def __init__(self):
        super().__init__("일반 회복 포션", "체력을 30% 회복시킵니다.", 1, 3)

class SpecialPotion(Potion, ConsumableItem, DropItem):

    def __init__(self):
        super().__init__("특별 회복 포션", "체력을 100% 회복시키지만 중독에 걸릴 수 있습니다.", 1, 1)
        
class Character(Unit, EquipableItem):
	def use_potion(self):
        if self.get_hp() >= self.get_max_hp():
            print("이미 최대 체력입니다.")
            return

        print("사용할 회복 물약을 선택하세요.")
        print("1. 일반 포션")
        print("2. 특별 포션")

        choice = int(input("선택 : "))

        if choice == 1:
            regular_potion = RegularPotion()
            regular_potion.item_info()
            sel = str(input("일반 포션을 사용하시겠습니까? y/n : "))
            if sel == 'y':
                regular_potion.use(self)
            else:
                Character.use_potion(self)
        elif choice == 2:
            special_potion = SpecialPotion()
            special_potion.item_info()
            sel = str(input("특별 포션을 사용하시겠습니까? y/n : "))
            if sel == 'y':
                special_potion.use(self)
            else:
                Character.use_potion(self)
        else:
            print("잘못된 선택입니다.")

 

포션 종류를 두 가지로 나누면서 포션의 하위클래스로 분리를 하고, 변수를 선언하면서 수량도 같이 입력했다. 이게 반영되면서 use_potion 메서드를 불러올 때마다 수량이 초기화되는 문제가 발생한 것 같았다. 앞서 클래스 변수는 해당 클래스 내의 모든 객체가 공유하는 변수라고 정리해놓고는 까먹은 게 분명하다.

 

문제가 무엇인지 인식했으니, 상위 클래스인 Potion 클래스에 클래스 변수로 선언했다. 하지만 아직 문제가 해결되지 않았다. 수량이 변경되는 과정이 잘 적용되었지만 일반 물약과 특별 물약이 수량을 공유하고 있었다.  상속을 이용하는 경우에는 모든 하위 클래스가 동일한 클래스 변수를 공유한다는 사실을 간과한 것이다.

 

class RegularPotion(Potion, ConsumableItem, DropItem):
    quantity = 3
    
class SpecialPotion(Potion, ConsumableItem, DropItem):
    quantity = 3

 

이렇게 수정을 하니 수량과 관련된 문제들이 해결되었다. 그래도 빠르게 해결할 수 있어서 뿌듯했다. 안 그랬으면 이것저것 시도해보며 시간을 낭비할 뻔 했다.


수량 문제를 해결하고나니 가장 큰 문제가 남아있었다. 바로 특별 물약을 사용할 경우 한번에 전체 체력을 회복하지만 확률적으로 캐릭터가 중독에 걸리는 효과를 넣는 것이었다. 자바에서 만들지 않은 기능이기 때문에 새로 만들어야 했다.

 

def use(self, character):
    is_poisoned = random.random() <= 0.2
    if is_poisoned:
        print(f"{character.get_name()}이(가) 특별 포션을 사용하였으나 중독에 걸렸습니다!")
        for _ in range(5):
            character.take_damage(10)
            print(f"{character.get_name()}의 체력이 10만큼 줄었습니다. 현재 체력: {character.get_hp()}")
        	print(f"{character.get_name()}이(가) 특별 포션을 사용하여 체력을 완전히 회복했지만, 중독에 걸렸습니다!")
        	character.set_hp(character.get_max_hp())
    else:
        print(f"{character.get_name()}이(가) 특별 포션을 사용하여 체력을 완전히 회복했습니다.")
        character.set_hp(character.get_max_hp())

 

일단 나중에 정리할 생각으로 메서드를 하나 만들었다. 이제 전투를 진행하는 메서드를 수정해야하고 캐릭터가 중독된 상태를 만드는 메서드를 추가해야 했다.

 

class Character(Unit, EquipableItem):
    def __init__(self):
        super().__init__("", 0, 0, 0)
        self._birth = ""
        self._level = 1
        self._exp = 0
        self._equipped_weapon = None
        self._base_attack = 0
        self.set_name()
        self.set_birth()
        default_weapon = Weapon()
        self.equip(default_weapon)
        self._max_exp = 100
        self._poisoned = False
        self._poison_turn = 0

    def set_poisoned(self, poisoned):
        self._poisoned = poisoned

    def apply_poison(self, character):
        if self.get_poisoned():
            character.take_damage(10)
            print(f"{character.get_name()}이(가) 중독상태로 체력이 10만큼 줄었습니다. 현재 체력: {character.get_hp()}")
            self._poison_turn -= 1
            if self._poison_turn == 0:
                print(f"{character.get_name()}이(가) 중독상태에서 벗어났습니다.")
                self._poisoned = False

 

Character 클래스의 __init__ 메서드가 점점 길어지는 게 영 불안하긴 하지만 다른 방법이 도저히 떠오르지 않았다. 이 부분에 대해서는 수업 시간에 물어봐야할 것 같다.

 

class Battle:
    @staticmethod
    def take_turn(attacker, defender):
        attacker.set_rand_attack()
        attack_damage = attacker.get_rand_attack()
        print(f"{attacker.get_name()} 이(가) {defender.get_name()}에게 {attack_damage}만큼의 데미지를 주었습니다.")
        defender.take_damage(attack_damage)
        print(f"{defender.get_name()}의 남은 체력: {defender.get_hp()} / {defender.get_max_hp()}")

        if attacker.get_poisoned():
            character.apply_poison(character)
        if defender.get_poisoned():
            character.apply_poison(character)

 

서로 전투를 주고 받고 있기 때문에 캐릭터가 어태커인 경우와 디펜더인 경우 모두 지정해줘야한다고 생각했다. 그랬더니 한 턴에 캐릭터가 두 번 중독 데미지를 받았다. 한 번에 어떻게 코드가 진행될 지 예상하고 쓸 수는 없는걸까 라는 생각이 들면서 슬퍼지긴 했지만 아직 배우는 과정이라고 생각하며 기운을 차려보았다.

 

    @staticmethod
    def take_turn(character, enemy):
        character.set_rand_attack()
        attack_damage = character.get_rand_attack()
        print(f"{character.get_name()} 이(가) {enemy.get_name()}에게 {attack_damage}만큼의 데미지를 주었습니다.")
        enemy.take_damage(attack_damage)
        print(f"{enemy.get_name()}의 남은 체력: {enemy.get_hp()} / {enemy.get_max_hp()}")

    @staticmethod
    def monster_turn(enemy, character):
        enemy.set_rand_attack()
        attack_damage = enemy.get_rand_attack()
        print(f"{enemy.get_name()} 이(가) {character.get_name()}에게 {attack_damage}만큼의 데미지를 주었습니다.")
        character.take_damage(attack_damage)
        print(f"{character.get_name()}의 남은 체력: {character.get_hp()} / {character.get_max_hp()}")

        if character.get_poisoned():
            character.apply_poison(character)

 

그런데 이 코드를 만들기까지 일을 두 번 하는 수준을 넘어서 여러 번 코드를 수정해야 했다. 두 번 중독 데미지를 받았으니 defender 부분만 남기면 되겠다 생각하고 코드를 수정했는데 몬스터가 defender가 됐을 경우를 미처 생각하지 못한 것이다. 결국 여러 번의 수정을 거친 끝에 캐릭터 턴과 몬스터 턴으로 메서드를 분리한 뒤, 몬스터 턴이 끝날 때 캐릭터가 중독 데미지를 받게끔 만들 수 있게 되었다.

 

아무래도 아직은 내 머릿 속에서 그려진 과정을 하나하나 풀어서 코드를 만들어줘야 한다는 게 조금 어려운 것 같다. 좀 더 많은 프로그램을 만들어보고 연습하면서 컴퓨터처럼 생각하는 방법을 터득해야 할 것 같다.