Set은 레드블랙트리를 활용해서 데이터가 삽입, 삭제될 때마다 자동으로 트리의 균형을 맞추며 정렬하는 자료구조이다.

레드블랙트리 알고리즘에 따른 Insert까지는 일단 구현을 해보았다.

 

#pragma once
#include "Node.h"

#include <queue>
#include <string>

template<typename DataType>
class Set
{
private:
    using MyNode = typename TreeNode<DataType>;

public:
    using Iterator = typename BidirectionalIterator<DataType>;

public:
    Set()
    {
        NilNode = new MyNode();
        NilNode->Color = MyNode::NodeColor::Black;

        NilNode->LeftChild = NilNode;
        NilNode->RightChild = NilNode;
    }

    void Insert(const DataType& _Data)
    {
        MyNode* NewNode = InsertNewElement(_Data);

        if (NewNode == nullptr)
        {
            return;
        }

        MySize++;

        //더블레드발생
        while(NewNode != nullptr && NewNode != RootNode && (NewNode->Color == MyNode::NodeColor::Red && NewNode->Parent->Color == MyNode::NodeColor::Red))
        {
            NewNode = Balancing(NewNode);
        }
    }

private:

    MyNode* InsertNewElement(const DataType& _Data)
    {
        MyNode* NewNode = nullptr;

        if (RootNode == nullptr)
        {
            NewNode = CreateNewNode(_Data);

            RootNode = NewNode;
            RootNode->Parent = NilNode;
            RootNode->Color = MyNode::NodeColor::Black;
        }
        else
        {
            MyNode* CurNode = RootNode;

            while (true)
            {
                if (_Data < CurNode->Data)
                {
                    if (CurNode->LeftChild == NilNode)
                    {
                        NewNode = CreateNewNode(_Data);

                        CurNode->LeftChild = NewNode;
                        NewNode->Parent = CurNode;

                        break;
                    }
                    else
                    {
                        CurNode = CurNode->LeftChild;
                    }
                }
                else if (_Data > CurNode->Data)
                {
                    if (CurNode->RightChild == NilNode)
                    {
                        NewNode = CreateNewNode(_Data);

                        CurNode->RightChild = NewNode;
                        NewNode->Parent = CurNode;

                        break;
                    }
                    else
                    {
                        CurNode = CurNode->RightChild;
                    }
                }
                if (_Data == CurNode->Data)
                {
                    return NewNode;
                }
            }
        }

        return NewNode;
    }

    MyNode* CreateNewNode(const DataType& _Data)
    {
        MyNode* NewNode = new MyNode();

        NewNode->Data = _Data;
        NewNode->Color = MyNode::NodeColor::Red;
        NewNode->LeftChild = NilNode;
        NewNode->RightChild = NilNode;
        
        return NewNode;
    }

    MyNode* Balancing(MyNode* _InsertedNode)
    {
        if (_InsertedNode->Parent == NilNode)
        {
            return _InsertedNode;
        }

        MyNode* UncleNode = nullptr;
        MyNode* ParentNode = _InsertedNode->Parent;
        MyNode* Ancestor = _InsertedNode->Parent->Parent;

        if (ParentNode == Ancestor->LeftChild)
        {
            UncleNode = Ancestor->RightChild;
        }
        else if (ParentNode == Ancestor->RightChild)
        {
            UncleNode = Ancestor->LeftChild;
        }

        MyNode* ReturnNode = nullptr;

        if (UncleNode->Color == MyNode::NodeColor::Black)
        {
            ReturnNode = Restructuring(_InsertedNode, UncleNode);
        }
        else if (UncleNode->Color == MyNode::NodeColor::Red)
        {
            ReturnNode = ReColoring(_InsertedNode, UncleNode);
        }

        return ReturnNode;
    }

    MyNode* ReColoring(MyNode* _InsertedNode, MyNode* _UncleNode)
    {
        _InsertedNode->Parent->Color = MyNode::NodeColor::Black;
        _UncleNode->Color = MyNode::NodeColor::Black;

        _InsertedNode->Parent->Parent->Color = MyNode::NodeColor::Red;

        RootNode->Color = MyNode::NodeColor::Black;

        return _InsertedNode->Parent->Parent;
    }

    MyNode* Restructuring(MyNode* _InsertedNode, MyNode* _UncleNode)
    {
        MyNode* ParentNode = _InsertedNode->Parent;
        MyNode* AncestorNode = _InsertedNode->Parent->Parent;

        if (ParentNode == AncestorNode->LeftChild && _InsertedNode == ParentNode->LeftChild)
        {
            if (ParentNode->RightChild != NilNode)
            {
                AncestorNode->LeftChild = ParentNode->RightChild;
                AncestorNode->LeftChild->Parent = AncestorNode;
            }

            ParentNode->RightChild = AncestorNode;
            ParentNode->Parent = AncestorNode->Parent;
            
            if (AncestorNode == RootNode)
            {
                RootNode = ParentNode;
            }
            else if (AncestorNode->Parent->LeftChild == AncestorNode)
            {
                AncestorNode->Parent->LeftChild = ParentNode;
            }
            else if (AncestorNode->Parent->RightChild == AncestorNode)
            {
                AncestorNode->Parent->RightChild = ParentNode;
            }

            AncestorNode->Parent = ParentNode;
            AncestorNode->LeftChild = NilNode;

            ParentNode->Color = MyNode::NodeColor::Black;
            ParentNode->LeftChild->Color = MyNode::NodeColor::Red;
            ParentNode->RightChild->Color = MyNode::NodeColor::Red;
        }
        else if (ParentNode == AncestorNode->LeftChild && _InsertedNode == ParentNode->RightChild)
        {
            _InsertedNode->LeftChild = ParentNode;
            _InsertedNode->RightChild = AncestorNode;

            ParentNode->RightChild = NilNode;
            AncestorNode->LeftChild = NilNode;

            _InsertedNode->Parent = AncestorNode->Parent;

            if (AncestorNode == RootNode)
            {
                RootNode = _InsertedNode;
            }
            else if (AncestorNode->Parent->LeftChild == AncestorNode)
            {
                AncestorNode->Parent->LeftChild = _InsertedNode;
            }
            else if (AncestorNode->Parent->RightChild == AncestorNode)
            {
                AncestorNode->Parent->RightChild = _InsertedNode;
            }

            ParentNode->Parent = _InsertedNode;
            AncestorNode->Parent = _InsertedNode;

            _InsertedNode->Color = MyNode::NodeColor::Black;
            _InsertedNode->LeftChild->Color = MyNode::NodeColor::Red;
            _InsertedNode->RightChild->Color = MyNode::NodeColor::Red;
        }
        else if (ParentNode == AncestorNode->RightChild && _InsertedNode == ParentNode->LeftChild)
        {
            _InsertedNode->LeftChild = AncestorNode;
            _InsertedNode->RightChild = ParentNode;

            ParentNode->LeftChild = NilNode;
            AncestorNode->RightChild = NilNode;

            _InsertedNode->Parent = AncestorNode->Parent;

            if (AncestorNode == RootNode)
            {
                RootNode = _InsertedNode;
            }
            else if (AncestorNode->Parent->LeftChild == AncestorNode)
            {
                AncestorNode->Parent->LeftChild = _InsertedNode;
            }
            else if (AncestorNode->Parent->RightChild == AncestorNode)
            {
                AncestorNode->Parent->RightChild = _InsertedNode;
            }

            ParentNode->Parent = _InsertedNode;
            AncestorNode->Parent = _InsertedNode;

            _InsertedNode->Color = MyNode::NodeColor::Black;
            _InsertedNode->LeftChild->Color = MyNode::NodeColor::Red;
            _InsertedNode->RightChild->Color = MyNode::NodeColor::Red;
        }
        else if (ParentNode == AncestorNode->RightChild && _InsertedNode == ParentNode->RightChild)
        {
            if (ParentNode->LeftChild != NilNode)
            {
                AncestorNode->RightChild = ParentNode->LeftChild;
                ParentNode->LeftChild->Parent = AncestorNode;
            }

            ParentNode->LeftChild = AncestorNode;
            ParentNode->Parent = AncestorNode->Parent;

            if (AncestorNode == RootNode)
            {
                RootNode = ParentNode;
            }
            else if (AncestorNode->Parent->LeftChild == AncestorNode)
            {
                AncestorNode->Parent->LeftChild = ParentNode;
            }
            else if (AncestorNode->Parent->RightChild == AncestorNode)
            {
                AncestorNode->Parent->RightChild = ParentNode;
            }

            AncestorNode->Parent = ParentNode;
            AncestorNode->RightChild = NilNode;

            ParentNode->Color = MyNode::NodeColor::Black;
            ParentNode->LeftChild->Color = MyNode::NodeColor::Red;
            ParentNode->RightChild->Color = MyNode::NodeColor::Red;
        }

        return nullptr;
    }

public:

	MyNode* NilNode = nullptr;
	MyNode* RootNode = nullptr;
	MyNode* EndNode = nullptr;

	size_t MySize = 0;
};

 

먼저, 처음 자료구조가 생성되면 NilNode를 만들어서 포인터를 저장해주었다.

그리고, 데이터가 삽입되면 양쪽 자식을 NilNode를 가리키게 하였고 루트노드라면 추가적으로 부모 노드또한 NilNode를 가리키도록 하였다.

 

데이터가 삽입되면, 먼저 크기에 따라 위치를 잡도록 하였다. 루트노드부터 시작해서 기준 노드보다 값이 작다면 왼쪽으로 값이 크다면 오른쪽으로 이동하도록 하였다.

 

이동이 완료되면 해당 노드와 해당 노드의 부모 노드의 색을 보고 정렬을 실시하도록 하였다. 만약, 해당 노드의 색이 Red이고 부모 노드의 색도 Red라면 레드블랙트리 알고리즘에선 균형을 잡는 과정을 거쳐야 하기 때문에 균형을 잡는 Balancing 함수를 실시하도록 하였다.

 

Balancing 함수 내부에선 삽입된 노드의 삼촌 노드를 확인하고, 삼촌 노드의 색에 따라 ReColoring혹은 ReStructuring 함수를 실시하도록 하였다.

 

Recoloring은 노드의 위치를 바꾸지 않고 색만 바꾸는 작업이고, ReStructuring는 노드의 위치를 바꾸는 작업이다.

두 함수를 통해 DoubleRed(빨간색 노드가 연속으로 이어진 상황)이 해결되고 나면 Insert 함수를 종료하도록 하였다.

원래는 node를 하나 만들어서 List와 Set,Map이 같이 사용하도록 하려했는데

당연하게도 둘이 사용해야 할 노드가 성격이 다르다.

 

그래서 Node를 외부로 빼서 TreeNode와 ListNode를 따로 만들어주었다. 하지만, 두 노드간의 일관적인 인터페이스가 있어야 양방향 이터레이터에서 동일한 방식으로 자료구조를 다룰 수 있기 때문에 Node라는 최상위 클래스를 만들어서 그 안에 순수 가상함수를 선언해놓고 이를 상속받도록 하였다.

#pragma once

template<typename DataType>
class Node
{
public:
    virtual void operator=(const Node<DataType>* _Node) = 0;

    virtual Node<DataType>* operator++() = 0;
    virtual Node<DataType>* operator++(int) = 0;
    virtual Node<DataType>* operator--() = 0;
    virtual Node<DataType>* operator--(int) = 0;

    virtual const DataType& Get() = 0;
};

template<typename DataType>
class ListNode : public Node<DataType>
{
public:
    void operator=(const Node<DataType>* _Node)
    {
        const ListNode<DataType>* CastedNode = dynamic_cast<const ListNode<DataType>*>(_Node);

        Data = CastedNode->Data;
        PrevNode = CastedNode->PrevNode;
        NextNode = CastedNode->NextNode;
    }

    ListNode<DataType>* operator++() override
    {
        *this = NextNode;
        return this;
    }

    ListNode<DataType>* operator++(int) override
    {
        ++(*this);
        return PrevNode;
    }

    ListNode<DataType>* operator--() override
    {
        *this = PrevNode;
        return this;
    }

    ListNode<DataType>* operator--(int) override
    {
        --(*this);
        return NextNode;
    }

    const DataType& Get() override
    {
        return Data;
    }

    DataType Data;
    ListNode<DataType>* PrevNode = nullptr;
    ListNode<DataType>* NextNode = nullptr;
};

template<typename DataType>
class TreeNode : public Node<DataType>
{
public:
    enum NodeColor
    {
        None,
        Red,
        Black,
    };

    void operator=(const Node<DataType>* _Node)
    {
    }

    TreeNode<DataType>* operator++() override
    {
        return this;
    }

    TreeNode<DataType>* operator++(int) override
    {
        return nullptr;
    }

    TreeNode<DataType>* operator--() override
    {
        return nullptr;
    }

    TreeNode<DataType>* operator--(int) override
    {
        return nullptr;
    }

    const DataType& Get() override
    {
        return Data;
    }

    DataType Data;
    
    NodeColor Color = NodeColor::None;

    TreeNode<DataType>* Parent = nullptr;
    TreeNode<DataType>* LeftChild = nullptr;
    TreeNode<DataType>* RightChild = nullptr;
};

 

TreeNode는 아직 함수 구현은 하지 않은 상태이다. 먼저 Set을 만들고 그에 맞게 구조를 맞춰볼 생각이다.

일단, 원래는 Node 클래스가 List 내부에 있었는데 이를 외부로 뺐다.

그리고 양방향 이터레이터, List, Map, Set이 모두 같은 Node클래스를 공유해서 구현되도록 할 예정이다.

 

이를 기반으로 양방향 이터레이터의 연산자를 몇개 오버로딩해보았다.

 

template<typename DataType>
class BidirectionalIterator
{
    using Node = typename Node<DataType>;
public:
    BidirectionalIterator(const Node* _Node)
    {
        NodePtr = const_cast<Node*>(_Node);
    }
    
    BidirectionalIterator(Node&& _Node)
    {
        NodePtr = _Node;

        _Node.PrevNode = nullptr;
        _Node.NextNode = nullptr;
    }

    const DataType& operator*()
    {
        return NodePtr->Data;
    }

    BidirectionalIterator& operator--()
    {
        NodePtr = NodePtr->PrevNode;
        return *this;
    }

    BidirectionalIterator operator--(int)
    {
        Node* ReturnNode = NodePtr;
        NodePtr = NodePtr->PrevNode;
        return BidirectionalIterator(ReturnNode);
    }

    BidirectionalIterator& operator++()
    {
        NodePtr = NodePtr->NextNode;
        return *this;
    }

    BidirectionalIterator operator++(int)
    {
        Node* ReturnNode = NodePtr;
        NodePtr = NodePtr->NextNode;
        return BidirectionalIterator(ReturnNode);
    }

private:
    Node* NodePtr = nullptr;
};

 

간단하게 추가를 했고, 제대로 사용하기 위해선 연산자가 좀 더 필요한데 일단 추후에 추가하기로 하고

List에도 이 이터레이터를 기반으로 Begin과 End를 추가하였다.

 

using Node = typename Node<DataType>;
using Iterator = typename BidirectionalIterator<DataType>;

일단 이렇게 using 구문을 추가해주고..

Iterator Begin()
{
    return Iterator(Head->NextNode);
}

Iterator End()
{
    return Iterator(Tail);
}

이렇게 begin과 end를 간단하게 추가하였다.,

Head와 Tail은 더미노드라서 Begin 은 Head의 NextNode를 해주었다.

End는 원래 마지막 데이터의 다음 노드를 가리키기 때문에 Tail을 그대로 리턴해주었다.

 

일단 테스트 해봤는데 컴파일도 잘되고 기능도 문제 없는듯 하다.

일단 다음에 연산자를 몇개 추가하고 List의 멤버함수를 좀 더 추가한 다음

Map과 Set을 구현해보아야겠다.

기존에는 커스텀예외객체만을 생성해놓고 try, catch는 모두 자료구조의 함수 안에서 실행하도록 했지만, 이젠 try catch를 호출하는 static 클래스를 만든 뒤, 자료구조에선 해당 함수를 호출하도록 하였다.

 

#pragma once
#include <cassert>
#include "CustomException.h"

enum class ExceptionType
{
    OutOfRange,
    EmptyContainer,
};

class ExceptionFunction
{
public:
    static void CheckException(bool _IsOccurred, bool _DoAssert, const char* _ClassName, ExceptionType _ExceptionType)
    {
        try
        {
            if (_IsOccurred == true)
            {
                ThrowException(_ClassName, _ExceptionType);
            }
        }
        catch (std::exception& _Error)
        {
            std::cerr << _Error.what() << std::endl;
            assert(_DoAssert);
        }
    }

private:
    static void ThrowException(const char* _ClassName, ExceptionType _ExceptionType)
    {
        switch (_ExceptionType)
        {
        case ExceptionType::OutOfRange:
            throw CustomException::OutOfLange(_ClassName);
            break;
        case ExceptionType::EmptyContainer:
            throw CustomException::EmptyContainer(_ClassName);
            break;
        }
    }
};

 

이렇게, 외부에선 CheckException 함수를 호출하여 파라미터를 잘 대입해준다면, 예외 발생 여부를 검사하고 그에 맞는 메세지를 출력하고 예외 객체를 던지고 assert하는 것까지 모두 해당 함수 내부에서 처리하도록 하였다.

virtual const bool& At(size_t _Index) 
{	
    try
    {
        if (_Index >= MySize)
        {
            throw CustomException::OutOfLange();
        }
    }
    catch (std::exception& _Error)
    {
        std::cerr << _Error.what() << std::endl;
        assert(false);
    }

    return MyElements[_Index];
}

위는 기존의 At 코드인데, 이 코드는 아래와 같이 바뀌었다.

virtual const bool& At(size_t _Index) 
{
    ExceptionFunction::CheckException(_Index >= MySize, true, typeid(*this).name(), ExceptionType::OutOfRange);
    return MyElements[_Index];
}

그리고, List도 일부 기능을 구현하였다.

먼저, List클래스 내부에서만 사용할 수 있는 Node 중첩 클래스를 선언하였고, 이를 통해 원소들이 연결되도록 하였다.

 

List는 Head와 Tail을 보유하고 있고, 더미노드를 생성하여 각각 더미노드를 가리키도록 하였다.

즉 List의 가장 앞의 원소는 Head의 다음 원소가 되는 셈이며, 가장 뒤의 원소는 Tail의 앞의 원소가 되는 것이다.

 

아래는 구현된 코드 전문이다.

#pragma once
#include "ExceptionFunction.h"

template <typename DataType>
class List
{
    class Node;
public:
    List()
    {
        CreateDummyNode();
    }

    List(size_t _Size)
    {
        CreateDummyNode();
        
        for (int i = 0; i < _Size; i++)
        {
            Push_Back(DataType());
        }
    }

    List(size_t _Size, const DataType& _Data)
    {
        CreateDummyNode();

        for (int i = 0; i < _Size; i++)
        {
            Push_Back(_Data);
        }
    }

    List(size_t _Size, DataType&& _Data)
    {
        CreateDummyNode();

        for (int i = 0; i < _Size; i++)
        {
            Push_Back(_Data);
        }
    }

public:
    void Push_Back(const DataType& _Data)
    {
        Node* NewNode = new Node();
        NewNode->DataPtr = _Data;

        Node* CurBackNode = Tail->PrevNode;
        CurBackNode->NextNode = NewNode;

        NewNode->PrevNode = CurBackNode;
        NewNode->NextNode = Tail;

        Tail->PrevNode = NewNode;

        MySize++;
    }

    void Push_Back(DataType&& _Data)
    {
        Node* NewNode = new Node();
        NewNode->DataPtr = _Data;

        Node* CurBackNode = Tail->PrevNode;
        CurBackNode->NextNode = NewNode;

        NewNode->PrevNode = CurBackNode;
        NewNode->NextNode = Tail;

        Tail->PrevNode = NewNode;

        MySize++;
    }

    const DataType& Front()
    {
        ExceptionFunction::CheckException(Head->NextNode == nullptr, true, typeid(*this).name(), ExceptionType::OutOfRange);
        return Head->NextNode->DataPtr;
    }

    const DataType& Back()
    {
        ExceptionFunction::CheckException(Tail->PrevNode == nullptr, true, typeid(*this).name(), ExceptionType::OutOfRange);
        return Tail->PrevNode->DataPtr;
    }

private:
    class Node
    {
    public:
        void operator=(const Node* _Node)
        {
            DataPtr = _Node->DataPtr;
            PrevNode = _Node->PrevNode;
            NextNode = _Node->NextNode;
        }

        const Node* operator++()
        {
            *this = NextNode;
            return *this;
        }

        const Node operator++(int)
        {
            Node ReturnNode = NextNode;
            ++(*this);
            return ReturnNode;
        }

        const Node* operator--()
        {
            *this = PrevNode;
            return *this;
        }

        const Node* operator--(int)
        {
            Node ReturnNode = PrevNode;
            --(*this);
            return ReturnNode;
        }

        DataType* DataPtr = nullptr;
        Node* PrevNode = nullptr;
        Node* NextNode = nullptr;
    };

private:
    void CreateDummyNode()
    {
        Node* DummyHead = new Node();
        Node* DummyTail = new Node();

        Head = DummyHead;
        Tail = DummyTail;

        DummyTail->PrevNode = Head;
        DummyHead->NextNode = Tail;
    }

private:
    Node* Head = nullptr;
    Node* Tail = nullptr;
    
    size_t MySize = 0;
};

 

기존에는 이터레이터가 벡터에 중첩클래스로 종속되어 있었고 이를 이용하는 형식이었는데, STL의 구조를 면밀히 살펴보니 Iterator은 완전히 별개의 클래스고 자료구조는 여기에 데이터를 담아 반환해줄 뿐인 것 같았다.

 

그래서 이터레이터를 아예 별개의 클래스로 분리해주었다.

이터레이터의 종류는 InputIterator, OutputIterator, forwardIterator, BiDirectionalIterator, RandomAccessIterator로 총 5가지가 있다고 하는데, 난 일단 벡터에서 사용할 RandomAccessIterator만 구현하였다.

template <typename DataType>
class RandomAccessIterator
{
public:
    RandomAccessIterator()
    {
    }

    RandomAccessIterator(const DataType* _DataPtr)
    {
        DataPtr = const_cast<DataType*>(_DataPtr);
    }

    RandomAccessIterator(const RandomAccessIterator& _Other)
    {
        DataPtr = _Other.DataPtr;
    }

    RandomAccessIterator& operator=(const RandomAccessIterator& _Other)
    {
        DataPtr = _Other.DataPtr;
        return *this;
    }

    RandomAccessIterator& operator=(RandomAccessIterator&& _Other) noexcept
    {
        DataPtr = _Other.DataPtr;
        _Other.DataPtr = nullptr;

        return *this;
    }

    bool operator==(const RandomAccessIterator& _Other)
    {
        return (DataPtr == _Other.DataPtr);
    }

    bool operator!=(const RandomAccessIterator& _Other)
    {
        return !(*this == _Other);
    }
    
    RandomAccessIterator& operator++()
    {
        (DataPtr)++;
        return *this;
    }

    RandomAccessIterator operator++(int)
    {
        RandomAccessIterator ReturnIter(*this);
        ++(*this);
        return ReturnIter;
    }

    RandomAccessIterator& operator--()
    {
        (DataPtr)--;
        return *this;
    }

    RandomAccessIterator operator--(int)
    {
        RandomAccessIterator ReturnIter(*this);
        --(*this);
        return ReturnIter;
    }
     
    RandomAccessIterator& operator+(int _Offset)
    {
        DataPtr += _Offset;
        return *this;
    }

    RandomAccessIterator& operator-(int _Offset)
    {
        DataPtr -= _Offset;
        return *this;
    }

    DataType& operator*()
    {
        return *(DataPtr);
    }

    void Debug()
    {
        std::cout << *(DataPtr);
    }

private:
    DataType* DataPtr = nullptr;
};

bool을 제외한 자료형에 대해서 위와 같이 선언해주었다. 기존의 이터레이터와 정의는 완전히 동일하다.

따로 수정을 안해도 될 것 같아서 그대로 외부로 꺼내기만 했는데, 이는 추후 테스트를 좀 해봐야 할 듯 하다.

 

bool타입이 문제가 좀 많았다. 먼저, *연산자를 사용하면 bool값을 반환해야 하는 동시에 *연산자로 반환된 데이터에 =연산자를 통해 값을 바꿀 수도 있어야 했다. 아무래도 bool 타입은 비트단위로 관리되다 보니 일반적인 포인터로 구현하는 것이 불가능했는데, 이를 중첩클래스를 활용해 해결하였다. 먼저 코드 전문을 보자.

template<>
class RandomAccessIterator<bool>
{
    class BitReference;

public:
    RandomAccessIterator()
    {
    }

    RandomAccessIterator(unsigned int* _DataPtr, size_t _BitIndex)
    {
        DataPtr = _DataPtr;
        BitIndex = _BitIndex;
    }

    RandomAccessIterator(const RandomAccessIterator& _Other)
    {
        DataPtr = _Other.DataPtr;
        BitIndex = _Other.BitIndex;
    }

    RandomAccessIterator& operator=(const RandomAccessIterator& _Other)
    {
        DataPtr = _Other.DataPtr;
        BitIndex = _Other.BitIndex;

        return *this;
    }

    RandomAccessIterator& operator=(RandomAccessIterator&& _Other) noexcept
    {
        DataPtr = _Other.DataPtr;
        BitIndex = _Other.BitIndex;
        _Other.DataPtr = nullptr;

        return *this;
    }

    bool operator==(const RandomAccessIterator& _Other)
    {
        return (DataPtr == _Other.DataPtr && BitIndex == _Other.BitIndex);
    }

    bool operator!=(const RandomAccessIterator& _Other)
    {
        return !(*this == _Other);
    }

    RandomAccessIterator& operator++()
    {
        BitIndex++;

        if (BitIndex >= 32)
        {
            BitIndex = 0;
            DataPtr++;
        }

        return *this;
    }

    RandomAccessIterator operator++(int)
    {
        RandomAccessIterator ReturnIter(*this);
        ++(*this);
        return ReturnIter;
    }

    RandomAccessIterator& operator--()
    {
        BitIndex--;

        if (BitIndex < 0)
        {
            BitIndex = 31;
            DataPtr--;
        }

        return *this;
    }

    RandomAccessIterator operator--(int)
    {
        RandomAccessIterator ReturnIter(*this);
        --(*this);
        return ReturnIter;
    }

    RandomAccessIterator operator+(int _Offset)
    {
        size_t ReturnBitIndex = BitIndex + _Offset;
        unsigned int* ReturnDataPtr = DataPtr;

        while (ReturnBitIndex >= 32)
        {
            ReturnBitIndex -= 32;
            ReturnDataPtr++;
        }

        return RandomAccessIterator(ReturnDataPtr, ReturnBitIndex);
    }

    RandomAccessIterator operator-(int _Offset)
    {
        size_t ReturnBitIndex = BitIndex - _Offset;
        unsigned int* ReturnDataPtr = DataPtr;

        while (ReturnBitIndex >= 32)
        {
            ReturnBitIndex += 32;
            ReturnDataPtr--;
        }

        return RandomAccessIterator(ReturnDataPtr, ReturnBitIndex);
    }

    BitReference operator*()
    {
        return BitReference(DataPtr, BitIndex);
    }

    void Debug()
    {
        std::cout << *(DataPtr);
    }

private:
    class BitReference
    {
    public:
        BitReference(const unsigned int* _DataPtr, size_t _BitIndex)
        {
            DataPtr = const_cast<unsigned int*>(_DataPtr);
            BitIndex = _BitIndex;
        }

        operator bool() const
        {
            return (*DataPtr) & (1 << BitIndex);
        }


        void operator=(bool _Value)
        {
            if (_Value == true)
            {
                (*DataPtr) |= (1 << BitIndex);
            }
            else
            {
                (*DataPtr) &= ~(1 << BitIndex);
            }
        }

    private:
        unsigned int* DataPtr = nullptr;
        size_t BitIndex = 0;
    };

private:
    unsigned int* DataPtr = nullptr;
    size_t BitIndex = 0;
};

먼저, *연산자를 호출하면 BitReference라는 클래스를 생성하여 이를 반환하도록 하였다. 그리고 BitReference 클래스 내부에 = 연산자를 오버로딩하여 이를 통해 값을 바꿀 수 있도록 하였다.

 

또한, *연산자를 통해 bool 값도 반환받을 수 있어야 하기 때문에 BitReference 클래스에 bool타입으로의 캐스팅 연산자를 오버로딩하여 값을 받을 수 있도록 하였다.

먼저, vector은 bool타입에 대해 특수화되어있다.

 

하지만, vector은 bool타입이라고 하더라도 동일한 인터페이스를 보유해야 한다.

그러므로, 상속을 통해 멤버함수들을 순수 가상함수로 선언해주었다.

template<typename DataType>
class VectorBase
{
public:
	class Iterator;

public:
	virtual void Reserve(const size_t _Capacity) = 0;
	virtual void Resize(const size_t _Size) = 0;
	virtual void Resize(const size_t _Size, const DataType& _Data) = 0;
	virtual void Resize(const size_t _Size, DataType&& _Data) = 0;
	
	virtual void Push_Back(const DataType& _Data) = 0;
	virtual void Push_Back(DataType&& _Data) = 0;
	virtual void Pop_back() = 0;
	
	virtual void Insert(const Iterator& _Where, const DataType& _Value) = 0;
	virtual void Clear() = 0;
	
	virtual const DataType& At(size_t _Index) = 0;
	virtual const DataType& Front() = 0;
	virtual const DataType& Back() = 0;
};

 

공통으로 구현해야 하는 함수들을 VectorBase라는 함수에 선언하였고, 이 클래스를 상속받도록 하였다.

또한, Iterator 클래스를 Vector이 아닌 VectorBase 내부로 이동시켜주었다.

 

이로 인해 템플릿 클래스의 중첩 클래스인 Iterator을 컴파일러가 혼동할 수 있으므로, Vector에는 아래의 구문을 추가해주었다.

using Iterator = typename VectorBase<DataType>::Iterator;

위의 구문으로 인해, 사용이 편해진 건 덤이다.  

 

그리고 이를 기반으로, bool 타입을 제외한 Vector의 멤버함수에 몇 가지 함수를 추가로 구현해주었다.

 

const DataType& Front() override
{
	return MyElements[0];
}

const DataType& Back() override
{
	return MyElements[MySize - 1];
}

void Clear() override
{
    MySize = 0;
    EndPtr = BeginPtr + 1;
}

void Pop_back() override
{
    if (MySize > 0)
    {
    	MySize--;
    	EndPtr--;
    }
}

void Insert(const Iterator& _Where, const DataType& _Value) override
{
    if (MySize + 1 >= MyCapacity)
    {
        Reserve(MyCapacity * 2);
    }

    Iterator EndIter = End();

    while (_Where != EndIter)
    {
        *EndIter = *(EndIter - 1);
    }
}

 

이제, Vector<bool> 타입에 대해 함수를 모두 구현해주면 Vector는 일단은 마무리이다.

디버깅을 위해 커스텀 예외처리 클래스를 만들었다. 물론 C++에서 제공하는 예외처리 클래스가 존재하지만, 그냥 만들어보았다. 별 것도 아니고 그냥 코드 몇 줄 적으면 되는거지만 아무튼 만들어보는게 도움이 될 것 같았다.

#pragma once
#include <exception>

class CustomException
{
public:
    class OutOfLange : public std::exception
    {
    public:
        OutOfLange()
        {

        }

        OutOfLange(const char* _ClassName)
        {
            ClassName = _ClassName;
        }

        const char* what() const noexcept override
        {
            static std::string ExMsg = "Exception is detected : OutOfLange\nClassName : " + ClassName;
            return ExMsg.c_str();
        }

    private:
        std::string ClassName = "";
    };
};

 

먼저, CustomException 클래스를 만들었고, 그 안에 예외처리 클래스를 넣어두었다.

CustomException은 일종의 네임스페이스처럼 사용되는 셈이다.

 

그 안에는 먼저, 범위를 초과할 때 사용될 OutOfLange 클래스를 만들었다. 해당 클래스는 std::excpetion 클래스를 상속받고 있으며 What 함수를 오버라이딩하였다.

 

생성자에서 클래스의 typeid를 받아 이를 저장하고, 예외처리 메세지에 사용하는 구조로 만들어주었다.

 

이 예외처리를 기반으로 Vector클래스 내부에 At함수를 만들었다.

 

DataType operator[](size_t _Index)
{
    return *(BeginPtr + _Index);
}

먼저, [] 연산자는 이렇게 예외처리없이 포인터에 접근하도록 하였다. 그렇기 때문에 사이즈를 벗어난 값이 인자로 들어오면 무슨 일이 발생할 지 예측할 수 없다.

 

DataType At(size_t _Index)
{
    try 
    {
        if (_Index >= MySize)
        {
            throw CustomException::OutOfLange(typeid(*this).name());
        }
    }
    catch (std::exception& _Error)
    {
        std::cerr << _Error.what() << std::endl;
    }

    return *(BeginPtr + _Index);
}

At함수에는 이처럼 예외처리를 해주었다.

인자가 벡터의 원소개수보다 크거나 같다면 예외를 반환하도록 하였다.

예외 객체는 위에서 만들었던 OutOfLange 클래스를 반환해주었다.

 

Catch에선 OutOfLange에서 오버라이딩한 what함수를 호출하여 에러메세지를 출력하도록 하였다.

벡터의 이터레이터를 구현하였다.

 

처음엔, IteratorBase를 구현하고 이를 모든 자료구조의 Iterator이 상속받도록 하여 Algorithm 함수가 다양한 Iterator에 대해 대응하도록 하고 싶었고, 또 순수가상함수를 이용해 이터레이터간의 공통적인 인터페이스를 구현하고 싶었다.

 

하지만, 공변의 문제로 가상함수화 하기 힘든 연산자들이 몇몇 있었다. 이 때문에 구조를 각 자료구조의 이터레이터에 맞게 Algorithm 함수를 여러개로 오버로딩하는 방식으로 하기로 하였다.

 

일단 벡터의 이터레이터는 아래와 같이 필요한 연산자만 구현해놓았다.

더 필요하다면 추가로 구현할 것이고, 일단은 이정도만 구현해놓았다.

template<typename DataType>
class Vector<DataType>::Iterator
{
public:
	Iterator()
	{
	}

	Iterator(const DataType* _DataPtr)
	{
		DataPtr = const_cast<DataType*>(_DataPtr);
	}

	Iterator(const Iterator& _Other)
	{
		DataPtr = _Other.DataPtr;
	}

	Iterator& operator=(const Iterator& _Other)
	{
		DataPtr = _Other.DataPtr;
		return *this;
	}

	Iterator& operator=(Iterator&& _Other)
	{
		DataPtr = _Other.ataPtr;
		_Other.DataPtr = nullptr;

		return *this;
	}

	bool operator==(const Iterator& _Other)
	{
		return (DataPtr == _Other.DataPtr);
	}

	bool operator!=(const Iterator& _Other)
	{
		return !(*this == _Other);
	}

	Iterator& operator++()
	{
		(DataPtr)++;
		return *this;
	}

	Iterator operator++(int)
	{
		Iterator ReturnIter(*this);
		++(*this);
		return ReturnIter;
	}
	 
	DataType operator*()
	{
		return *(DataPtr);
	}

	void Debug()
	{
		std::cout << *(DataPtr);
	}

private:
	DataType* DataPtr = nullptr;
};

 

벡터의 멤버함수에는 위의 이터레이터를 토대로 Begin과 End를 추가하였다.

Iterator Begin()
{
    return Iterator(BeginPtr);
}

Iterator End()
{
    return Iterator(EndPtr);
}

 

여러 자료구조간의 공통의 인터페이스를 구현하기 위해 상속을 사용할 필요는 있을 것 같은데, 아직은 어떤 식으로 디자인 할 지 감이 잘 안온다.

일단 벡터를 모두 마무리 하고, 리스트까지 만든 다음에 두 클래스의 구조를 비교하면서 고민해봐야 할 듯 하다.

 

이번엔 전투의 일부를 구현하였다.

 

아래는 전투가 이루어지는 레벨의 코드 전체이다.

더보기
using System;
using System.Collections.Generic;
using System.Diagnostics.SymbolStore;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace C_Study
{
    public class FightLevel : GameLevel
    {
        public override void Update()
        {
            Console.Clear();
            
            if(SelectedMonster == null)
            {
                SelectMonster();
            }
            else
            {
                Fight();
            }
        }

        public FightLevel(GameCore parrentCore)
        {
            _parentCore = parrentCore;
            _playerLog = null;
            _monsterLog = null;
        }

        public void Fight()
        {
            if(_selectedMonster == null)
            {
                return;
            }

            DevFunctions.WriteLineColored("플레이어 정보", ConsoleColor.Blue);
            Console.WriteLine("HP : {0} / {1}", _parentCore.CurrentPlayer.CurrentHP, _parentCore.CurrentPlayer.MaxHP);
            Console.WriteLine();

            DevFunctions.WriteLineColored("몬스터 정보", ConsoleColor.Red);
            Console.WriteLine("이름 : {0}", _selectedMonster.Name);
            Console.WriteLine("HP : {0} / {1}", _selectedMonster.CurrentHP, _selectedMonster.MaxHP);
            Console.WriteLine();

            if (_selectedMonster.CurrentHP <= 0)
            {
                Win();   
            }
            else if(_parentCore.CurrentPlayer.CurrentHP <= 0)
            {
                Lose();   
            }
            else
            {
                WriteFightLog();
                SelectBehavior();
                MonsterBehavior();
            }
        }

        private void Win()
        {
            Console.WriteLine("전투에서 승리하였습니다!");

            _parentCore.CurrentPlayer.CurrentEXP += _selectedMonster.WinEXP;
            _parentCore.CurrentPlayer.Money += _selectedMonster.WinMoney;

            Console.WriteLine("아무 키나 입력시 메뉴 선택창으로 돌아갑니다.");

            Console.Read();

            _selectedMonster = null;
            _monsterLog = null;
            _playerLog = null;

            _parentCore.CurrentLevel = LevelType.Menu;
        }
        private void Lose()
        {
            Console.WriteLine("전투에서 패배하였습니다..");
            Console.WriteLine("아무 키나 입력시 메뉴 선택창으로 돌아갑니다.");

            Console.Read();

            _selectedMonster = null;
            _monsterLog = null;
            _playerLog = null;

            _parentCore.CurrentPlayer.CurrentHP = 1;
            _parentCore.CurrentLevel = LevelType.Menu;
        }


        private void WriteFightLog()
        {
            DevFunctions.WriteLineColored("나의 행동", ConsoleColor.Blue);
            
            if(_playerLog != null)
            {
                Console.WriteLine("{0}", _playerLog);
                Console.WriteLine();
            }

            DevFunctions.WriteLineColored("적의 행동", ConsoleColor.Red);
            if(_monsterLog != null)
            {
                Console.WriteLine("{0}", _monsterLog);
            }

            Console.WriteLine();
        }

        private void SelectBehavior()
        {
            Console.WriteLine("1. 공격한다.\n2. 아이템을 사용한다.\n3. 도망친다.");

            string input = Console.ReadLine();

            if (DevFunctions.IsNumeric(input) == false)
            {
                return;
            }

            if (input.Length > 1)
            {
                return;
            }

            int toInt = int.Parse(input);

            switch (toInt)
            {
                case 1:
                    PlayerAttack();
                    break;
            }
        }

        private void PlayerAttack()
        {
            int Damage = GameFunctions.GetCalculatedDamage(_parentCore.CurrentPlayer, _selectedMonster);
            bool isCritical = GameFunctions.isCritical(_parentCore.CurrentPlayer);

            string CriticalMsg = "";

            if(isCritical == true) 
            {
                Damage = (int)(Damage * _parentCore.CurrentPlayer.CriticalPower);
                CriticalMsg = "[크리티컬!] ";
            }

            _playerLog = "적에게 공격 : " + CriticalMsg + Damage.ToString() + "의 피해를 입혔습니다.";

            _selectedMonster.CurrentHP -= Damage;
        }
        private void MonsterBehavior()
        {
            if(_selectedMonster.CurrentHP <= 0)
            {
                _monsterLog = "사망하였습니다.";
                return;
            }

            int Damage = GameFunctions.GetCalculatedDamage(_selectedMonster, _parentCore.CurrentPlayer);
            bool isCritical = GameFunctions.isCritical(_selectedMonster);

            string CriticalMsg = "";

            if (isCritical == true)
            {
                Damage = (int)(Damage * _selectedMonster.CriticalPower);
                CriticalMsg = "[크리티컬!] ";
            }

            _monsterLog = "플레이어에게 공격 : " + CriticalMsg + Damage.ToString() + "의 피해를 입혔습니다.";

            _parentCore.CurrentPlayer.CurrentHP -= Damage;
        }

        private void SelectMonster()
        {
            DevFunctions.WriteLineColored("전투하고 싶은 몬스터를 선택하세요.", ConsoleColor.DarkCyan);
            Console.WriteLine();

            Console.WriteLine("1. 고블린 (적정 레벨 : 1)");
            Console.WriteLine("2. 오우거 (적정 레벨 : 5)");
            Console.WriteLine("3. 드래곤 (적정 레벨 : 10)");
            Console.WriteLine("4. 이전으로 돌아간다.");

            string input = Console.ReadLine();

            if (DevFunctions.IsNumeric(input) == false)
            {
                return;
            }

            if (input.Length > 1)
            {
                return;
            }

            int toInt = int.Parse(input);

            switch (toInt)
            {
                case 1:
                    _selectedMonster = new Goblin();
                    break;
                case 4:
                    _parentCore.CurrentLevel = LevelType.Menu;
                    _selectedMonster = null;
                    break;
            }
        }

        public static Monster SelectedMonster
        {
            get { return _selectedMonster; }
            set { _selectedMonster = value; }
        }

        private static Monster _selectedMonster;
        private static string _playerLog;
        private static string _monsterLog;
    }
}

현재는 공격에 대한 기능만 구현이 되어있고, 아이템 사용은 아직 구현하지 않았다.

공격을 실시하면 몬스터의 HP가 깎이고 몬스터도 플레이어에게 즉시 공격을 실시한다.

몬스터의 HP가 0이 되면, Win함수가 호출되고  적의 HP가 0이되면 Lose함수가 호출된다.

 

Win함수가 호출되면, 플레이어는 경험치와 돈을 획득한다.

 

아래는 플레이어 경험치 변수의 set이 호출될 때 작업을 작성한 것이다.

여기서 레벨 업이 진행되도록 하였다.

더보기
public int CurrentEXP
{
    get { return _currentEXP; }
    set 
    {
        _currentEXP = value;

        if (_currentEXP >= _maxEXP)
        {
            LevelUp();
        }
    }
}

private void LevelUp()
{
    _currentEXP -= _maxEXP;
    Console.WriteLine("레벨이 상승하였습니다!");

    MaxEXP = (int)(_maxEXP * 1.1f);

    _level++;

    _attPower += 10;
    _defPower += 2;
}

레벨이 오르면, 경험치 수치가 조정되고 공격력과 방어력이 일정 수치만큼 오르도록 하였다.

최대 경험치 수치도 동시에 증가한다.

 

아래 코드는 데미지를 계산하는 것과 크리티컬 여부를 판단하는 함수이다.

더보기
public class GameFunctions
{
    public static int GetCalculatedDamage(ICreatureStat attacking, ICreatureStat attacked)
    {
        Random random = new Random();

        int MinAtt = (int)(attacking.AttPower * 0.9f);
        int MaxAtt = (int)(attacking.AttPower * 1.1f);

        int Att = random.Next(MinAtt, MaxAtt);
        int Def = attacked.DefPower;

        float DefRate = Def / 100.0f;
        DefRate = 1.0f - DefRate;

        return (int)(Att * DefRate);
    }

    public static bool isCritical(ICreatureStat attacking)
    {
        Random random = new Random();
        int randomValue = random.Next(0, 100);

        if (randomValue <= attacking.CriticalProb_100)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}

 

이 함수를 활용하여 최종 데미지를 산출하였다.

 

아래 동영상은 구현된 결과이다.

 

이렇게 공격을 주고받는 기능은 구현하였다.

다음 번엔 아이템을 사용하는 것과 무기의 특수효과를 적용할 예정이다.

 

그리고 추가적으로 인벤토리 아이템을 출력할 때, 아이템을 이름 순으로 정렬하도록 했는데 정렬 방법이 C++과 조금 달랐다. C++은 비교 연산자를 오버로딩하여 사용하지만 C#은 IComparable 인터페이스를 상속받은 뒤 CompareTo라는 함수를 구현해야 정렬이 가능했다. 

 int IComparable<Item>.CompareTo(Item other)
 {
     if(other.Name.CompareTo(Name) > 0)
     {
         return -1;
     }
     else
     {
         return 1;
     }
 }

 그래서 Item 클래스에 요런 함수를 추가로 작성해주었다.

일단 전투를 추가하기 전에 상점 기능을 추가하였다.

private Tuple<string, ConsoleColor> _shopMsg;

먼저, 상점의 상단에 출력할 메세지를 저장할 튜플을 하나 선언하였다.

여기엔 어떤 물품의 구매를 성공했는지, 혹은 소지 금액이 모자라서 실패했는지 등을 알려주는 문자열이 저장된다.

item2가 출력할 색상이다.

 

아래는 상점 코드이다.

더보기
private void SelectShopItem()
{
    DevFunctions.WriteLineColored("구매하고 싶은 물건의 번호를 입력하세요.", ConsoleColor.Cyan);
    Console.WriteLine(); 

    Console.Write("1. 사과 : ");
    DevFunctions.WriteColored("30원", ConsoleColor.Red);
    DevFunctions.WriteLineColored("[사용시 HP를 50 회복합니다.]", ConsoleColor.Green);

    Console.Write("2. 배 : ");
    DevFunctions.WriteColored("50원", ConsoleColor.Red);
    DevFunctions.WriteLineColored("[사용시 HP를 100 회복합니다.]", ConsoleColor.Green);

    Console.Write("3. 필살의 영약 : ");
    DevFunctions.WriteColored("200원", ConsoleColor.Red);
    DevFunctions.WriteLineColored("[사용시 다음 공격의 크리티컬 확률이 50% 증가합니다.]", ConsoleColor.Green);

    Console.WriteLine("4. 구매를 종료합니다.");

    string input = Console.ReadLine();

    if (DevFunctions.IsNumeric(input) == false)
    {
        return;
    }

    if (input.Length > 1)
    {
        return;
    }

    int toInt = int.Parse(input);

    switch (toInt)
    {
        case 1:
            BuyItem(ItemName.사과);
            break;
        case 2:
            break;
        case 3:
            break;
        case 4:
            _updateFunc = null;
            _selectFunc = new SelectFunc(SelectMenu);
            _shopMsg = null;
            break;
    }
}

물건을 선택하면 BuyItem이라는 함수를 호출한다.

더보기
 private void BuyItem(ItemName itemName)
 {
     if (Item.isCanBuy(_parentCore.CurrentPlayer, itemName) == true)
     {
         SetShopMsg_Buy(itemName);
         Item.BuyItem(_parentCore.CurrentPlayer, itemName);
     }
     else
     {
         SetShopMsg_Failed();
     }
 }
 
 private void SetShopMsg_Buy(ItemName itemName)
{
    _shopMsg = new Tuple<string, ConsoleColor>(itemName.ToString() + "를 구매하였습니다", ConsoleColor.Blue);
}

private void SetShopMsg_Failed()
{
    _shopMsg = new Tuple<string, ConsoleColor>("소지 금액이 모자랍니다.", ConsoleColor.Red);
}

레벨에 함수는 이렇게 설정되어 있고, 내부 코드를 보면 Item 클래스의 static 함수를 사용하고 있는데 코드는 아래와 같다.

더보기
public static bool isCanBuy(Player player, ItemName itemName)
{
    switch(itemName)
    {
        case ItemName.사과:
            return (player.Money >= 30);
    }

    return false;
}

public static void BuyItem(Player player, ItemName itemName)
{
    switch (itemName)
    {
        case ItemName.사과:
            player.Inventory.Add(new Apple());
            player.Money -= 30;
            break;
    }
}

물품을 구매할 수 있는지 확인하는 함수와 실제로 구매하는 함수이다.

 

아래 코드를 통해, 상단에 소지 금액과 함께 메세지가 출력된다. 그 아래엔 위에서 본 선택 메세지가 출력되는 것이다.

더보기
private void WriteShopMsg()
{
    string Str = "소지 금액 : " + _parentCore.CurrentPlayer.Money.ToString();
    DevFunctions.WriteLineColored(Str, ConsoleColor.Yellow);

    if (_shopMsg != null)
    {
        DevFunctions.WriteLineColored(_shopMsg.Item1, _shopMsg.Item2);
    }
}

 

구현된 결과다.

 

또한, 아이템을 구매한 뒤 현재 소지 품목을 출력하는 기능도 추가하였다.

물품을 구매한 뒤 2번 인벤토리를 확인한다를 선택하면 아래와 같이 소지 품목이 출력된다.

 

상점 기능을 추가하였고, 전투 기능을 추가하기 위해 몬스터와 플레이어에 공통의 인터페이스를 하나 상속하였다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace C_Study
{
    public interface ICreatureStat
    {
        int AttPower { get; }
        int DefPower { get; }
    }
}

그냥 공격력, 방어력을 get하는 함수만 있다.

 

이걸 상속하도록 한 이유는 데미지 계산을 하기 위해서인데, 공격자와 피격자가 몬스터인지 플레이어인지 알 필요 없이 공격력과 방어력만으로 계산하기 위해서이다.

public static int GetFinalAttPower(ICreatureStat attacking, ICreatureStat attacked)
{
    int Att = attacking.AttPower;
    int Def = attacked.DefPower;
    
    float DefRate = Def / 100.0f;
    DefRate = 1.0f - DefRate;
    
    return (int)(Att * DefRate);
}

위의 함수를 호출하면, 공격자가 몬스터인지 플레이어인지 알 필요 없이 결과적인 데미지만 얻어낼 수 있다.

이 외에도 필요한 기능이 있으면 인터페이스에 추가하면 될 것 같다.

 

생각해보니 아이템을 구현할 때 enum을 한글로 사용해보았다.

 public enum ItemName
 {
     사과,
     배,
     필살의영약,
 }

그냥 되나 궁금해서 해봤는데 되길래 걍 써봤다 ㅋㅋ

은근 편한 부분이 있긴 한데, 다시는 쓸 일 없을듯

+ Recent posts