반응형

 

 

※ 명령패턴 설명

 

명령 패턴 (Command pattern)

1. 명령패턴 (Command pattern) 명령(이동, 점프 등)에 대한 함수를 클래스로 래핑하여 해당 클래스를 객체화 한 것이다. 사용자(캐릭터)를 명령에 대한 매개변수로 만들어 요청을 대기시키거나 로깅

srdeveloper.tistory.com


 

 

 

 

1. 실행 취소 기능

명령 패턴으로 실행 단위를 객체로 만들어두면 손쉽게 실행취소 기능을 구현할 수 있다.

위와 같이 Command 객체에 대한 List를 만들고 추가 명령이 생성되면 List의 맨 끝에 추가한 뒤, 현재 명령으로 기억하면 된다. Undo의 기능만 넣게되면 현재명령은 지워주고 현재 명령을 이전 명령으로 위치를 잡아주면 되고, Redo 기능을 같이 추가하게되면 실행 취소 전의 명령은 그대로 살려주면 될것이다.

 

 

 

2. 예시 코드

2-1) Command(추상 클래스)

public abstract class Command
{
    protected int direction;
    protected Actor actor;

    public abstract void Execute(Actor actor);
    public abstract void ExecuteAxis(Actor actor);
    public abstract void Undo();
}

 

2-2) VerticalCommand(Command 상속)

using UnityEngine;

public class VerticalCommand : Command
{
    public VerticalCommand(Actor actor, int direction)
    {
        this.actor = actor;
        this.direction = direction;
    }
    public override void Execute(Actor actor)
    {
        
    }

    public override void ExecuteAxis(Actor actor)
    {
        actor.MoveTo(new Vector2(0, direction));
    }

    public override void Undo()
    {
        actor.MoveTo(new Vector2(0, -direction));
    }
}

 

2-3) HorizontalCommand(Command 상속)

using UnityEngine;

public class HorizontalCommand : Command
{
    public HorizontalCommand(Actor actor, int direction)
    {
        this.actor = actor;
        this.direction = direction;
    }
    public override void Execute(Actor actor)
    {
        
    }

    public override void ExecuteAxis(Actor actor)
    {
        actor.MoveTo(new Vector2(direction, 0));
    }

    public override void Undo()
    {
        actor.MoveTo(new Vector2(-direction, 0));
    }
}

 

2-4) Actor

using UnityEngine;

public class Actor : MonoBehaviour
{
    private Rigidbody2D rb;

    private void Start()
    {
        rb = GetComponent<Rigidbody2D>();
    }

    public void MoveTo(Vector2 pos)
    {
        rb.MovePosition(rb.position + pos);
    }
}

 

2-5) InputHandler

using System.Collections.Generic;
using UnityEngine;

public class InputHandler : MonoBehaviour
{
    [SerializeField]
    private Actor targetActor;

    private List<Command> commands = new List<Command>();
    private int executionIndex = -1;

    private void Update()
    {
        Command targetCommand = HandleInput();
        if(targetCommand != null && targetActor != null)
        {
            commands.Add(targetCommand);
            executionIndex++;

            targetCommand.ExecuteAxis(targetActor);
        }
    }

    private Command HandleInput()
    {
        if (Input.GetKeyDown(KeyCode.W))
        {
            return new VerticalCommand(targetActor, 1);
        }
        else if (Input.GetKeyDown(KeyCode.S))
        {
            return new VerticalCommand(targetActor, - 1);
        }
        else if (Input.GetKeyDown(KeyCode.A))
        {
            return new HorizontalCommand(targetActor, - 1);
        }
        else if (Input.GetKeyDown(KeyCode.D))
        {
            return new HorizontalCommand(targetActor,1);
        }
        else if(executionIndex >= 0 && Input.GetKey(KeyCode.LeftControl) && Input.GetKeyDown(KeyCode.X))
        {
            commands[executionIndex].Undo();
            commands.RemoveAt(executionIndex);
            executionIndex--;
        }

        return null;
    }
}

 

※ 위 코드는 Undo의 기능만 구현해서 짠 코드이다.

※ 생성자에 Actor를 인자로 받는 이유는 플레이어만이 아닌 다른 캐릭터에 대한 실행취소도 포함하기 위함이다.

※ 앞선 커맨트 패턴의 게시글의 코드를 재활용해서 짠 코드이므로 Actor에 붙어있는 Rigidbody의 GravityScale의 값을 0으로 설정해야 정상적으로 작동 된다.

 

 

 

3. 실행 확인

 

반응형

'Stack > DesignPattern' 카테고리의 다른 글

명령 패턴 (Command pattern)  (0) 2024.02.23
반응형

 

 

 

 

1. 명령패턴 (Command pattern)

명령(이동, 점프 등)에 대한 함수를 클래스로 래핑하여 해당 클래스를 객체화 한 것이다.

사용자(캐릭터)를 명령에 대한 매개변수로 만들어 요청을 대기시키거나 로깅하여 되돌리기 기능의 연산을 지원할 수도 있다.

※ 일반적으로 실행 취소 기능을 구현하려면 힘들지만 명령 패턴을 사용하면 쉬워진다.

 

 

 

2. 명령패턴 예시 구조 및 코드

InputHandler 클래스에서 틱마다 키 입력을 받을 수 있는 로직을 구성하고, 점프키에 해당하는 조건문이 통과하게 되면 JumpCommand 객체를 반환하고 반환된 Command 객체에 Actor 객체를 매개변수를 넘겨서 Command의 Execute 함수에서 Actor의 Jump함수를 호출해주는 구조이다.

 

2-1) Command(추상 클래스)

public abstract class Command
{
    public abstract void Execute(Actor actor);
}

 

2-2) JumpCommand(Command 상속)

public class JumpCommand : Command
{
    public override void Execute(Actor actor)
    {
        actor.Jump();
    }
}

 

2-3) Actor

using UnityEngine;

public class Actor : MonoBehaviour
{
    private Rigidbody2D rb;

    private void Start()
    {
        rb = GetComponent<Rigidbody2D>();
    }

    public void Jump()
    {
        rb.AddForce(Vector2.up * 5f, ForceMode2D.Impulse);
    }
}

 

2-4) InputHandler

using UnityEngine;

public class InputHandler : MonoBehaviour
{
    [SerializeField]
    private Actor targetActor;

    private Command jumpCommand = new JumpCommand();

    private void Update()
    {
        Command targetCommand = HandleInput();
        if(targetCommand != null && targetActor != null)
        {
            targetCommand.Execute(targetActor);
        }
    }

    private Command HandleInput()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            return jumpCommand;
        }

        return null;
    }
}

 

각 명령(Command)에 대한 객체를 생성함으로써 직접적으로 함수를 호출하는 방식 대신, 한 겹 우회하는 계층을 만들어 입력 키의 교체가 가능하게 되었고, 각 명령 객체의 Execute에 Actor를 인수로 받게 함으로써 플레이어 캐릭터만이 아닌 여러 NPC, 몬스터 등의 캐릭터도 조작이 가능할 수 있도록 구성되어 있다.

※ 키 변경의 경우, 멤버 변수의 객체를 교환함으로써 키 변경이 가능하다는걸 알려주는건 이해하는데, 이게 과연 효율적인가 싶은 의문이 든다. 차라리 키 변경은 Command 객체를 교체하는것이 아닌 키를 변수로 둬서 키 값을 교체하는게 더 효율적일거같은 생각이 든다.

 

 

 

 

 

반응형

'Stack > DesignPattern' 카테고리의 다른 글

명령패턴 실행취소 기능  (0) 2024.02.27
반응형

 

 

 

 

 

풀이 코드

using System;
using System.Collections.Generic;

public class Solution {
    private HashSet<int> m_visited = new HashSet<int>();

    public int solution(int n, int[,] computers) {
        int answer = 0;
        for(int i = 0; i < n; i++)
        {
            if(!m_visited.Contains(i))
            {
                answer++;
                DFS(computers, i);
            }
        }
        return answer;
    }

    private void DFS(int[,] computers, int currentIndex)
    {
        m_visited.Add(currentIndex);
        for(int i = 0; i < computers.GetLength(0); i++)
        {
            if(m_visited.Contains(i) || currentIndex == i ||
              computers[currentIndex, i] == 0)
            {
                continue;
            }

            DFS(computers, i);
        }
    }
}
  • 재귀함수를 이용하여 DFS를 구현하였다. (Stack 및 반복문을 이용하는 방법도 있지만, 코드가 복잡하게 보일까봐 재귀함수로 작성)

 

 

 

 

기타

- 그래프를 평소에 다루어 보지 않아서 한참을 헤맸다. (통과는 됐지만 이게 진짜 문제에서 요구하는게 맞는지 헷갈림)

 

 

 

 

 

반응형

'Stack > Coding test' 카테고리의 다른 글

[C# / Lv3] 이중우선순위큐  (0) 2022.10.10
[C# / Lv2] 올바른 괄호  (0) 2022.10.07
[C# / Lv2] 이진 변환 반복하기  (0) 2022.10.07
[C# / Lv2] 최솟값 만들기  (0) 2022.10.07
[C# / Lv2] JadenCase 문자열 만들기  (0) 2022.10.07
반응형

 

 

 

 

 

풀이 코드

using System;
using System.Collections.Generic;

public class Solution {
    public int[] solution(string[] operations) {
        Heap heap = new Heap();
        for(int i = 0; i < operations.Length; i++)
        {
            string[] split = operations[i].Split(" ");
            if(split[0] == "I")
            {
                heap.Add(Int32.Parse(split[1]));
            }
            else
            {
                if(split[1] == "1")
                {
                    heap.RemoveMax();
                }
                else
                {
                    heap.RemoveMin();
                }
            }
        }
        int[] answer = heap.GetAnswer();
        return answer;
    }
}

public class Heap
{
    private List<int> m_minHeap = new List<int>();
    private List<int> m_maxHeap = new List<int>();

    public void Add(int n)
    {
        m_minHeap.Add(n);
        SortUp(m_minHeap, CompareMin);
        m_maxHeap.Add(n);
        SortUp(m_maxHeap, CompareMax);
    }

    public void RemoveMin()
    {
        if(m_minHeap.Count <= 0 || m_maxHeap.Count <= 0)
        {
            return;
        }

        m_minHeap[0] = m_minHeap[m_minHeap.Count - 1];
        m_minHeap.RemoveAt(m_minHeap.Count - 1);
        SortDown(m_minHeap, CompareMin);

        m_maxHeap.RemoveAt(m_maxHeap.Count - 1);
    }

    public void RemoveMax()
    {
        if(m_minHeap.Count <= 0 || m_maxHeap.Count <= 0)
        {
            return;
        }

        m_maxHeap[0] = m_maxHeap[m_maxHeap.Count - 1];
        m_maxHeap.RemoveAt(m_maxHeap.Count - 1);
        SortDown(m_maxHeap, CompareMax);

        m_minHeap.RemoveAt(m_minHeap.Count - 1);
    }

    public int[] GetAnswer()
    {
        int[] answer = new int[] { 0, 0 };
        if(m_maxHeap.Count > 0 && m_minHeap.Count > 0)
        {
            answer[0] = m_maxHeap[0];
            answer[1] = m_minHeap[0];
        }

        return answer;
    }

    private void SortDown(List<int> list, Func<int, int, int> compare)
    {
        int currentIndex = 0;
        while(true)
        {
            int leftIndex = currentIndex * 2 + 1;
            int rightIndex = currentIndex * 2 + 2;
            int swapIndex = currentIndex;

            if(leftIndex < list.Count)
            {
                swapIndex = leftIndex;

                if(rightIndex < list.Count)
                {
                    if(compare.Invoke(list[leftIndex], list[rightIndex]) < 0)
                    {
                        swapIndex = rightIndex;
                    }
                }

                if(compare.Invoke(list[currentIndex], list[swapIndex]) < 0)
                {
                    Swap(list, currentIndex, swapIndex);
                    currentIndex = swapIndex;
                }
                else
                {
                    return;
                }
            }
            else
            {
                return;
            }
        }
    }

    private void SortUp(List<int> list, Func<int, int, int> compare)
    {
        int currentIndex = list.Count - 1;
        int parentIndex = currentIndex / 2;
        while(true)
        {
            if(compare.Invoke(list[currentIndex], list[parentIndex]) > 0)
            {
                Swap(list, currentIndex, parentIndex);
                currentIndex = parentIndex;
                parentIndex = currentIndex / 2;
            }
            else
            {
                break;
            }
        }
    }

    private void Swap(List<int> list, int indexA, int indexB)
    {
        int tmp = list[indexA];
        list[indexA] = list[indexB];
        list[indexB] = tmp;
    }

    private int CompareMax(int a, int b)
    {
        return a.CompareTo(b);
    }

    private int CompareMin(int a, int b)
    {
        return -CompareMax(a, b);
    }
}
  • 최소값을 루트에 갖는 List와 최대값을 루트에 갖는 List 2개를 만들어주고, 값이 들어가거나 빠질때마다 SortUp, SortDown으로 리스트를 갱신해준다.
  • 최종 결과값은 최소 List와 최대 List의 루트값을 출력해주면 된다.

 

 

 

 

기타

- 문제 카테고리가 힙으로 분류되어있어서 힙을 구현해서 사용했다. (다른사람 풀이를 보니 Linq를 사용해서 짧게 작성했던데, 카테고리 생각하면 Linq사용하는게 정답같아보이지는 않는다.)

- 힙에 대한 설명은 아래 포스팅 참조

 

[C#] 자료구조 힙(Heap) 트리 구현

1. 힙(Heap) - 최대값 혹은 최소값을 빠르게 찾아낼 수 있도록 고안된 완전이진트리 자료구조이다. - 부모노드와 자식노드의 키 값 사이에 대소관계가 성립해야하는 조건을 만족해야한다. - 부모노

srdeveloper.tistory.com

 

 

 

 

 

 

반응형
반응형

 

 

 

 

 

풀이 코드

using System;

public class Solution {
    public bool solution(string s) {
        int count = 0;
        for(int i = 0; i < s.Length; i++)
        {
            count = s[i] == '(' ? count + 1 : count - 1;
            if(count < 0)
            {
                return false;
            }
        }

        return count == 0;
    }
}
  • 문자열을 순회하면서 '('값이면 count 값을 올리고, ')'이면 값을 내린다.
  • 순회 도중 count가 음수가 되면 괄호 짝이 안맞는것이므로 false로 출력한다.
  • 모든 문자열을 순회하였을 시, count 값이 0이면 true, 아니면 false로 출력한다.

 

 

 

기타

- 카테고리가 Stack/Queue로 되어있어서 스택을 써야하나 고민했는데, 굳이 스택을 써야하나 싶어서 위와 같은 과정으로 풀이함.

 

 

 

 

 

반응형

+ Recent posts