트랙
/
Python
Python
/
연습 문제
/
Ellen의 외계인 게임
Ellen의 외계인 게임

Ellen의 외계인 게임

학습 연습 문제

소개

Python의 객체 지향 프로그래밍

함수형, 선언형, 명령형 스타일로 프로그래밍해 왔다면, 객체 지향 프로그래밍(OOP)으로 초점을 옮기는 것이 조금 낯설게 느껴질 수 있어요. OOP 접근 방식은 문제를 서로 상호작용하고, 상태를 유지하며, 데이터에 작용하는 하나 이상의 objects로 모델링하는 것을 생각해 보라고 프로그래머에게 요청해요. 객체는 현실 세계의 개체(예를 들어 자동차나 고양이)를 나타낼 수도 있고, 더 추상적인 개념(예를 들어 정수, 탈것, 포유류)을 나타낼 수도 있어요. 각 객체는 컴퓨터 메모리에서 고유한 인스턴스가 되고, 전체 모델의 일부를 나타내요.

클래스

Classes는 새로운 객체 타입의 정의이고, 여기서 새로운 객체 instances가 만들어져요. 클래스는 흔히 데이터를, 그 데이터를 다루는 코드나 함수와 함께 묶어요. 이런 의미에서 클래스는 비슷한 타입의 객체를 많이 만들어 사용할 수 있는 청사진 또는 일련의 지침이에요. 복잡한 프로그램은 클래스를 많이 가질 수 있고, 각 클래스는 여러 가지 서로 다른 객체를 만들어요. 클래스로부터 객체를 만드는 과정을 instantiation(또는 클래스의 인스턴스를 만드는 것)이라고 해요.

Python에서 클래스 정의는 간단해요:

class MyClass:
    # Class body goes here

클래스 속성

클래스 필드(달리 properties, attributes, data members, variables라고도 해요)는 클래스 본문에 추가할 수 있어요:

class MyClass:
    number = 5
    string = "Hello!"

MyClass의 인스턴스(객체)는 클래스를 호출해서 만들고 이름에 묶을 수 있어요(함수를 호출하는 것과 같은 방식이에요):

>>> new_object = MyClass()

# Class is instantiated and resulting object is bound to the "new_object" name.
# Note: the object address 'at 0x15adc55b0' will vary by computer.
>>> new_object
<__main__.MyClass at 0x15adc55b0>

Class attributes는 클래스에서 만들어진 모든 객체(또는 인스턴스)가 공유하고, dot notation, 즉 객체 이름 뒤에 속성 이름 앞에 놓는 .을 통해 접근할 수 있어요:

>>> new_object = MyClass()

# Accessing the class attribute "number" via dot-notation.
>>> new_object.number
5

# Accessing the class attribute "string" via dot-notation.
>>> new_object.string
'Hello!'

# Instantiating an additional object and binding it to the "second_new_object" name.
>>> second_new_object = MyClass()

>>> second_new_object
# Note: the object address "at 0x15ad99970" will vary by computer.
<__main__.MyClass at 0x15ad99970>

# Second_new_object shares the same class attributes as new_object.
>>> new_object.number == second_new_object.number
True

클래스 속성은 다른 어떤 메서드보다도 먼저, 클래스 자체의 본문에 정의해요. 클래스 속성은 클래스가 소유하기 때문에, 클래스의 인스턴스들 사이에서 공유될 수 있어요. 이 속성들은 공유되기 때문에, 클래스에서 직접 값에 접근하고 조작할 수 있어요. 클래스 속성의 값을 바꾸면 클래스에서 만들어진 모든 객체의 값이 바뀌어요:

>>> obj_one = MyClass()
>>> obj_two = MyClass()

# Accessing a class attribute from an object.
>>> obj_two.number
5

# Accessing the class attribute from the class itself.
>>> MyClass.number
5

# Modifying the value of the "number" class attribute.
>>> MyClass.number = 27

# Modifying the "number" class attribute changes the "number" attribute for all objects.
>>> obj_one.number
27

>>> obj_two.number
27

항상 데이터가 동기화된 객체 여러 개를 갖는 것은 그다지 유용하지 않아요. 다행히, 클래스에서 만든 객체는 고유한 instance attributes(또는 인스턴스 속성, 변수, 필드)로 커스터마이즈할 수 있어요. 이름에서 알 수 있듯이, 인스턴스 속성은 각 객체에 고유하고, 독립적으로 수정할 수 있어요.

__init__()로 객체 생성 커스터마이즈하기

특별한 "던더 메서드"("double underscore method"의 줄임말)인 __init__()는 클래스 인스턴스를 커스터마이즈하는 데 쓰이고, 객체의 인스턴스 속성이나 프로퍼티를 초기화하는 데 사용할 수 있어요. 인스턴스 속성을 초기화하는 역할 때문에, __init__()은 class constructor 또는 initializer라고도 해요. __init__()는 self라는 필수 매개변수 하나를 받는데, 이는 새로 초기화되거나 만들어진 객체를 가리켜요. 그런 다음 인스턴스 속성이나 프로퍼티를 위한 데이터를 self 매개변수 뒤에 __init__()의 인자로 전달할 수 있어요.

아래에서 MyClass는 location_x와 location_y라는 인스턴스 속성을 가져요. 보다시피, 두 속성은 __init__()에 전달된 location(튜플) 인자의 첫 번째와 두 번째 인덱스에 할당됐어요. 이제 클래스를 인스턴스화해서 객체를 만들면 클래스 인스턴스의 location_x와 location_y 속성이 초기화돼요:

class MyClass:
    # These are class attributes, variables, or fields.
    number = 5
    string = "Hello!"

    # This is the class "constructor", with a "location" parameter that is a tuple.
    def __init__(self, location):

        # This is an instance or object property, attribute, or variable.
        # Note that we are unpacking the tuple argument into two separate instance variables.
        self.location_x = location[0]
        self.location_y = location[1]

# Create a new object "new_object_one", with object property (1, 2).
>>> new_object_one = MyClass((1, 2))

# Create a second new object "new_object_two" with object property (-8, -9).
>>> new_object_two = MyClass((-8, -9))

# Note that new_object_one.location_x and new_object_two.location_x two different values.
>>> new_object_one.location_x
1

>>> new_object_two.location_x
-8

위에서 MyClass를 초기화할 때는 인자 하나만 전달하면 된다는 점에 주의해요. Python이 클래스를 호출할 때 self를 전달하는 것을 알아서 처리해 줘요.

메서드

method는 클래스 자체(클래스 메서드라고 하며, 나중 연습 문제에서 다뤄요)나 클래스의 인스턴스(객체)에 묶인 function이에요. 객체(인스턴스)를 다루는 메서드는 self를 첫 번째 매개변수로 해서 정의해야 해요. 그러고 나서 나머지 매개변수는 "일반" 함수, 즉 묶이지 않은 함수에서처럼 정의하면 돼요:

class MyClass:
    number = 5
    string = "Hello!"

    # Class constructor.
    def __init__(self, location):
        # Instance properties
        self.location_x = location[0]
        self.location_y = location[1]

    # Instance method. Note "self" as first parameter.
    def change_location(self, amount):
        self.location_x += amount
        self.location_y += amount
        return self.location_x, self.location_y

속성 접근과 마찬가지로, 메서드 호출은 객체 이름 뒤에, 메서드 이름 앞에 .을 놓기만 하면 돼요. 호출된 메서드는 첫 번째 매개변수로 객체의 복사본을 필요로 하지 않아요. Python이 self를 자동으로 채워 줘요:

class MyClass:
    number = 5
    string = "Hello!"

    def __init__(self, location):
        self.location_x = location[0]
        self.location_y = location[1]

    def change_location(self, amount):
        self.location_x += amount
        self.location_y += amount
        return self.location_x, self.location_y

# Make a new test_object with location (3,7)
>>> test_object = MyClass((3,7))
>>> (test_object.location_x, test_object.location_y)
(3,7)

# Call change_location to increase location_x and location_y by 7.
>>> test_object.change_location(7)
(10, 14)

클래스 속성은 클래스 밖에서 접근하는 것과 같은 방식으로 인스턴스 메서드 안에서 접근할 수 있어요:

class MyClass:
    number = 5
    string = "Hello!"

    def __init__(self, location):
        self.location_x = location[0]
        self.location_y = location[1]

    # Alter instance variable location_x and location_y
    def change_location(self, amount):
        self.location_x += amount
        self.location_y += amount
        return self.location_x, self.location_y

    # Alter class variable number for all instances from within an instance.
    def increment_number(self):
        # Increment the 'number' class variable by 1.
        MyClass.number += 1


>>> test_object_one = MyClass((0,0))
>>> test_object_one.number
5

>>> test_object_two = MyClass((13, -3))
>>> test_object_two.increment_number()
>>> test_object_one.number
6

Pass로 구현 자리 표시하기

이전 개념 연습 문제와 실습 연습 문제 스텁에서, 실제 코드 대신 함수 본문 안에 pass 키워드가 사용된 것을 봤을 거예요. pass 키워드는 문법적으로 유효한 자리 표시자예요. 빈 함수나 클래스 정의에 대해 Python이 문법 오류를 던지는 것을 막아 줘요. 본질적으로, 이는 Python 인터프리터에게 '걱정 마세요! 언젠가는 분명히 여기에 코드를 넣을 거예요, 아직 안 했을 뿐이에요.'라고 말하는 방법이에요.

class MyClass:
    number = 5
    string = "Hello!"

    def __init__(self, location):
        self.location_x = location[0]
        self.location_y = location[1]

    # Alter instance variable location_x and location_y
    def change_location(self, amount):
        self.location_x += amount
        self.location_y += amount
        return  self.location_x, self.location_y

    # Alter class variable number for all instances
    def increment_number(self):
        # Increment the 'number' class variable by 1.
        MyClass.number += 1

    # This will compile and run without error, but has no current functionality.
    def pending_functionality(self):
       # Stubbing or placholding the body of this method.
       pass

지침

Ellen은 플레이어가 외계인과 싸워야 하는 게임을 만들고 있어요. 막 객체 지향 프로그래밍(OOP)을 배운 참이라, classes를 사용하면 프로그램에 어떤 도움이 될지 기대에 부풀어 있어요.

Ellen은 기뻐하며 여러분에게 도움을 청했고, 여러분은 플레이어가 싸워야 할 외계인을 프로그래밍하는 일을 맡게 되었어요.

1. Alien 클래스 만들기

두 매개변수 <x_coordinate>, <y_coordinate>를 받는 생성자를 가진 Alien 클래스를 정의하고, 이 값들을 x_coordinate와 y_coordinate 인스턴스 변수에 넣어요. 또한 모든 외계인은 체력 3으로 시작하므로, health 변수도 함께 초기화해야 해요.

>>> alien = Alien(2, 0)
>>> alien.x_coordinate
2
>>> alien.y_coordinate
0
>>> alien.health
3

이제 각 외계인은 자신의 위치와 체력을 내부적으로 추적할 수 있어야 해요.

2. hit 메서드

Ellen은 Alien class에, 호출되면 외계인 객체의 체력을 1만큼 줄여 주는 hit 메서드가 있으면 좋겠다고 해요. 이렇게 하면 외계인의 체력을 직접 바꾸지 않고도 <alien>.hit()을 호출하기만 하면 돼요. hit()이 체력을 0까지 낮출지, 0 아래로도 내려가게 할지는 여러분이 정하면 돼요.

>>> alien = Alien(0, 0)

# Initialized health value.
>>> alien.health
3

# Decrements health by 1 point.
>>> alien.hit()
>>> alien.health
2

3. is_alive 메서드

체력이 계속 줄어들다 보면 언젠가는 0이 되거나, 심지어 그보다 더 낮아질 수도 있다는 걸 알게 될 거예요. 그럴 때 Ellen이 외계인이... 음... 살아 있는지 빠르게 확인할 수 있도록 is_alive 메서드를 추가하면 좋겠어요. 😉 <alien>.is_alive()는 불리언을 반환해야 해요.

>>> alien.health
1
>>> alien.is_alive()
True
>>> alien.hit()
>>> alien.health
0
>>> alien.is_alive()
False

4. teleport 메서드

Ellen의 게임에서 외계인은 순간이동 능력을 가지고 있어요! 새로운 x_coordinate와 y_coordinate 값을 받아 그에 맞게 외계인의 좌표를 바꾸는 teleport 메서드를 작성해야 해요.

>>> alien.teleport(5, -4)
>>> alien.x_coordinate
5
>>> alien.y_coordinate
-4

5. collision_detection 메서드

외계인이 무언가에 맞을 수 있다면, 당연히 그런 충돌이 일어났을 때 이를 감지할 수 있어야 해요. 하지만 충돌 감지 알고리즘은 까다로울 수 있고, 아직 어떻게 구현해야 할지 모르죠. Ellen은 나중에 직접 하겠다고 했지만, 기능을 구현해야 한다는 걸 잊지 않도록 collision_detection 메서드를 클래스에 넣어 두고 싶어 해요. 이 메서드는 어떤 종류의 변수(아마도 다른 객체)를 받아야 하지만, 아직 아는 건 그것뿐이에요. 메서드 정의를 클래스에 넣었을 때 호출해도 오류가 나지 않도록 해야 해요:

>>> alien.collision_detection(other_object)
>>>

6. Alien 개수 세기

Ellen이 새로운 부탁을 가지고 돌아왔어요. 게임이 진행되는 동안 외계인이 몇 개 만들어졌는지 추적하고 싶대요. 점수 시스템과 관련이 있다고 말해요.

예를 들어:

>>> alien_one = Alien(5, 1)
>>> alien_one.total_aliens_created
1
>>> alien_two = Alien(3, 0)
>>> alien_two.total_aliens_created
2
>>> alien_one.total_aliens_created
2
# Accessing the variable from the class directly
>>> Alien.total_aliens_created
2

7. Alien 배열 만들기

Ellen은 지금까지 해준 게 마음에 쏙 들지만, 부탁이 하나 더 있어요. Alien() 클래스 바깥에 있는 독립적인 함수를 원하는데, 이 함수는 위치들의 배열(tuples 형태)을 받아 Alien() 객체들의 list를 만들어요.

예를 들어:

>>> alien_start_positions = [(4, 7), (-1, 0)]
>>> aliens = new_aliens_collection(alien_start_positions)
...
>>> for alien in aliens:
...     print(alien.x_coordinate, alien.y_coordinate)
(4, 7)
(-1, 0)
GitHub에서 편집 링크가 새 창이나 탭에서 열려요
Python Exercism

Ellen의 외계인 게임 문제를 시작해 볼 준비가 됐나요?

Exercism에 가입하고 Python 트랙을 개념 17개연습 문제 146개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.