2021-12-22(Pythonゲーム3:シンプルな回転RPG)


今週の計画:20日月曜日から22日水曜日にかけてPythonゲームプロジェクトを行います/23日木曜日から24日金曜日にかけてKakaoTalk UIクローン、InstarUIクローンプロジェクトを行い、GITによるコラボレーションのための特別講座(午後2時~4時予定)/週の資料構造アルゴリズム講座を行い、WebプログラミングA to Zとネット時代の襲撃課題を復習します
午前9時、ゲド町の大講堂で、本日午後4時までに提出しなければならない簡単なPythonゲームの説明を聞いて、プロジェクトを行いました.
今日のプロジェクトは简単だと言っています.これはかなり時間がかかるプロジェクトで、すでに実施されていますが、1,2ゲームプロジェクトよりも知っておくべき事項や不足している部分が多いです.
[simple Explanation]
Pythonゲーム3:シンプルな回転RPGゲーム
(1)「モンスター」「プレイヤー」はそれぞれ存在し,<名称/HP/攻撃力>の共通属性を持つ.
(2)「モンスター」と「プレイヤー」はいずれも自分のターンに一定の動作をすることができ、1つの動作しか選択できない.
(3)「プレイヤー」ターン:特定の「モンスター」を攻撃/特定の「モンスター」を法師に使う
(4)「モンスター」ターン:「プレイヤー」への攻撃/体力回復/スタンバイ
(5)現在の状況は「モンスター」と「プレイヤー」の3:1対峙状況である.
(6)以上の説明に基づき、「プレイヤー」が「戦士」を操作して3匹の「モンスター」と戦うことを体現している.
🍍 今日の収穫🍍 :
(1)多形性:多様な形状(事前の美).プログラム設計言語の資料型体系の性質を表し,プログラム言語の各要素が多様な資料型に属することは許容される性質である.1つのオブジェクトに複数のタイプがあることを示します.一言で言えば、形は同じですが、機能は違います.例えば、同じスマートフォンでも、通話やゲーム、学習など様々な方法で使用でき、形成的といえる.
  • オーバーライド:クラスを継承するときに、親クラスで定義されたメソッドを子クラスで再定義します.ただし、親クラスで定義したメソッドの機能は、子クラスでは使用できません.主にサブクラスで特定の機能を変更または追加するために使用されます.
  • overloading:パラメータに基づいて複数の同名関数を定義し、それぞれ異なる機能を持つようにします.Pythonでは正式なサポートはありません.
    cf.super関数(super():親クラスのメソッドはサブクラスで使用され、必要な部分を再定義するだけで使用できます.('super().メソッド名(パラメータ)として)
  • <コメントリンク>
    面接問題#1
    [python入門]14。Python overridingとoverloading
    Pythonチュートリアル–クラスとオブジェクト~変数を人間に進化
    (2)全体コードを上に置き,確認コード中のコメントとして記述する.
  • 謙虚に知っている事項や不足点を学び、コードを少し修正します.
  • クラス作成部
  • # 클래스 부분
    
    # [[[ 알아야 될 사항 ]]]
      # 1. 클래스를 만들때 이름이나 체력등을 지정하는게 아니고 인스턴스를 만들때! 비로소 지정하는 것이 맞다.
      # why? 클래스라는 설계도로 수많은 인스턴스를 생성할 수 있을텐데 인스턴스들이 각각 다르게 지정될 수 있기 때문이다.
      # 2. 상속을 받을때는 새로운 속성이 추가되지 않은 이상 __init()__은(생성자는) 사용하지 않는 것이 좋다.
      # why? 부모 클래스의 생성자 속성들이 이미 상속되었는데 생성자 메서드 오버라이딩을 하면 코드 중복 및 부모 클래스의 생성자로 설정해둔 공통 속성에서 어긋날 수 있기 때문이다.
      # 파이썬에서 메서드 오버라이딩시, 부모 클래스에서 정의한 메서드의 기능을 자식 클래스에서 재활용이 불가능하다.
    
    class Object:
        def __init__(self, name, hp, physical_pow):
            self.name = name
            self.hp = hp
            self.physical_pow = physical_pow
            print(f'{self.name} 이(가) 생성 되었습니다.')
            print(f'체력 : {self.hp}, 물리 공격력 : {self.physical_pow}')
        
        def attack(self, target):
            print(f'{self.name}{target.name}{self.physical_pow}의 물리 공격력으로 공격했습니다.')
            target.hp -= self.physical_pow # target_hp - 자신 공격력 = target의 남은 hp!
            # 심화 개념: 사실은 여기서 target.hp 를 할당한다고 해서 실제로 해당 target 의 hp 값이 바뀌면 안되는 것이 원칙.
            # 그러므로 함수 마지막에서 return target.hp 를 해서 해당 target 의 값에 재할당해주는 것이 맞음.
            # 그러나 파이썬의 경우 자동으로 target이 가진 속성의 주소를 변경해주기에, 
            # 굳이 재할당해주지 않아도, 해당 target 의 hp 값이 바뀜.
            return target.hp # 재할당 해줌.
    
        def status(self):
            if self.hp > 0:
              print(f'{self.name}의 체력 : {self.hp}')
    
    
    class Player(Object):
        def __init__(self,  name, hp, physical_pow, magic_pow=50):
            Object.__init__(self, name, hp, physical_pow)
            self.magic_pow = magic_pow
    
        def magic_attack(self, target):
            print(f'{self.name}{target.name}{self.magic_pow}의 마법 공격력으로 공격했습니다.')
            target.hp -= self.magic_pow
            return target.hp
    
    
    class Monster(Object):
        def cure(self):
            # 여기서 생각해 볼 수 있는것은 max_hp 설정해놓고 그 이상 회복 못하게끔 제한하는 것.
            self.hp += 10
            print(f'{self.name}가 자신의 체력을 10만큼 회복했습니다. 현재 체력 : {self.hp}')
            return self.hp
    
        def wait(self):  
            print(f'{self.name}가 대기했습니다.')
  • 関数生成部
  • # 함수 부분
    
    # [[[ 알아야 될 사항 ]]]
    # 1. player_action(Player, Monsters) 이런식으로 다양한 인스턴스에 대처할 수 있도록 큰 범주가 공통적인 인자로 지정해주는게 좋다!
    # 2. 함수별로 return을 잘 작성해줘야한다! > 중복 참조 인자가 외부에 있을경우에 문제발생 시 대처가 어려워질 수 있기때문
    
    def player_action():
        print('< 공격 스킬 선택 phase >')
        print('1. 물리 공격')
        print('2. 마법 공격')
        p1_skill_input = int(input('사용할 스킬의 번호를 입력하세요. : '))
    
        print('< 대상 선택 phase >')
        print('1.고블린')
        print('2.오크')
        print('3.빅보스 Waaah')
        p1_target_input = int(input('공격할 대상의 번호를 입력하세요. : '))
    
    # <<< 부족했던 부분 >>>
    # 1. 대상 지정을 숫자로 일일히 한 것.
    # 2. 생성한 대상들이 리스트나 딕셔너리로 묶여 있어야 한번에 표시하기 쉬워진다.
    # 3. 딕셔너리로 묶여 있을 경우 키값으로 찾는 형식이 가능해지기에 더 직관적이게 된다.
    # 한마디로 아래와 같이 작성시 수많은 인스턴스의 파도가 칠때 답이 없어진다.
    
        # 물리 공격시
        if p1_skill_input == 1:
            # 공격 대상 고블린
            if p1_target_input == 1:
                m1.hp = p1.attack(m1)
            # 공격 대상 오크
            elif p1_target_input == 2:
                m2.hp = p1.attack(m2)
            # 공격 대상 빅보스 Waaah
            elif p1_target_input == 3:
                m3.hp = p1.attack(m3)
        # 마법 공격시
        elif p1_skill_input == 2:
            # 공격 대상 고블린
            if p1_target_input == 1:
                m1.hp = p1.magic_attack(m1)
            # 공격 대상 오크
            elif p1_target_input == 2:
                m2.hp = p1.magic_attack(m2)
            # 공격 대상 빅보스 Waaah
            elif p1_target_input == 3:
                m3.hp = p1.magic_attack(m3)
    
    def monster_action():
        # 살아있는 몬스터들만 행동
            if m1 in mon_list:
                # 고블린 행동
                action_num = rd.randint(1, 3) # 행동 번호 결정
                if action_num == 1: # 자신 치유
                    m1.cure()
                elif action_num == 2: # 대기
                    m1.wait()
                elif action_num == 3: # 플레이어 공격
                    p1.hp = m1.attack(p1)
            if m2 in mon_list:
                # 오크 행동
                action_num = rd.randint(1, 3)
                if action_num == 1:
                    m2.cure()
                elif action_num == 2:
                    m2.wait()
                elif action_num == 3:
                    p1.hp = m2.attack(p1)
            if m3 in mon_list:
                # 빅보스 Waaah 행동
                action_num = rd.randint(1, 3)
                if action_num == 1:
                    m3.cure()
                elif action_num == 2:
                    m3.wait()
                elif action_num == 3:
                    p1.hp = m3.attack(p1)
    
    # 공격 종료 후 몬스터들 죽음상태 체크
    # 이 부분에서도 리스트에서 제거하는것이 아닌 hp가 0인 죽음 상태에서 리스트에 존재하도록 하는게 더 나을것으로 생각된다.
    # 환생 등 고려 가능
    def monster_death():
        if m1.hp <= 0: # 고블린 hp가 0 이하이면
            if m1 in mon_list: # 몬스터 리스트에 고블린 요소가 존재하면
                mon_list.remove(m1) # 몬스터 리스트에서 고블린 요소 제거
        if m2.hp <= 0:
            if m2 in mon_list:
                mon_list.remove(m2)
        if m3.hp <= 0:
            if m3 in mon_list:
                mon_list.remove(m3)
  • ゲーム実行部
  • # 게임 실행 부분
    
    import random as rd
    
    # <<< 부족했던 부분 >>>
    # 1. 만약 플레이어나 몬스터의 숫자가 훨씬 많았다면 이렇게 작성해서는 택도 없다. 
    # 루프를 돌리면서 리스트 혹은 딕셔너리에 저장하는것이 맞겠다.
    
    p1 = Player('워리어', 100, 10)
    m1 = Monster('고블린', 10, 10)
    m2 = Monster('오크', 30, 30)
    m3 = Monster('빅보스 Waaah', 50, 50)
    
    mon_list = [m1, m2, m3]
    
    turn = 0
    
    while True:
    
        # 턴 시작전 총 상태표시
        print('===== 턴 시작전 총 상태표시 =====')
        p1.status() # 플레이어
        for m in mon_list: # 몬스터들
            m.status()
    
    
        # 플레이어 턴
        if turn % 2 == 0:
            print('===== 플레이어 턴 =====')
            
            # 플레이어 액션
            player_action()
    
            # 몬스터 사망 여부 체크
            monster_death()
    
            # 몬스터 리스트에 몬스터가 없을 경우
            if len(mon_list) <= 0:
                print('모든 몬스터를 물리쳤습니다!')
                print('===== Win! =====')
                break
    
    
        # 몬스터 턴
        else:
            print('===== 몬스터 턴 =====')
            
            # 몬스터 액션
            monster_action()
    
            # 공격 종료 후 플레이어 상태 체크
            if p1.hp <= 0:
                print('플레이어 사망!')
                print('== Game Over ==')
                break
    
        turn += 1
        print('-------------------')
        print('----- 턴 종료 -----')
        print('-------------------')