[멋쟁이사자처럼 부트캠프 TIL회고] 47일차 : 몬스터 상태
코딩테스트 : 소인수분해
소인수분해란 어떤 수를 소수들의 곱으로 표현하는 것입니다. 예를 들어 12를 소인수 분해하면 2 * 2 * 3 으로 나타낼 수 있습니다. 따라서 12의 소인수는 2와 3입니다. 자연수 n이 매개변수로 주어질 때 n의 소인수를 오름차순으로 담은 배열을 return하도록 solution 함수를 완성해주세요.
처음에는 아무 생각 없이 다음과 같이 코드를 짰다.
using System;
using System.Collections.Generic;
public class Solution
{
public int[] solution(int n)
{
List<int> answer = new List<int>();
for (int i=2; i <= n; i++)
{
if (n%i == 0)
{
for (int j = 0; j < answer.Count; j++)
{
if(answer[j] != i)
{
answer.Add(i);
}
}
}
}
return answer.ToArray();
}
}
세 가지 문제가 있다.
- answer[j]는 처음에 아무 것도 들어가 있지 않다. 따라서 비교할 수 없으므로 answer.Add(i)는 동작하지 않는다.
- i =2부터 i++하며 나눠 주는 것은 '소수'로 나눠주는 것이 아니다. 따라서 리스트에 소수가 아닌 수가 추가될 수 있다.
두 가지 문제를 해결하기 위해 다음과 같이 코드를 고쳐주었다.
using System;
using System.Collections.Generic;
public class Solution
{
public int[] solution(int n)
{
List<int> answer = new List<int>();
answer.Add(0);
for (int i=2; i <= n; i++)
{
if (n%i == 0)
{
for (int j = 0; j < answer.Count; j++)
{
if (answer[j] != i)
{
answer.Add(i);
i--;
}
}
}
}
return answer.ToArray();
}
}
다음과 같이 짜주었더니 시간초과가 나온다.
다시 !
using System;
using System.Collections.Generic;
public class Solution
{
public int[] solution(int n)
{
List<int> answer = new List<int>();
answer.Add(0);
for (int i=2; i*i <= n; i++)
{
while (n % i == 0)
{
if (!answer.Contains(i))
{
answer.Add(i);
}
n = n / i;
}
}
if (!answer.Contains(n))
{
answer.Add(n);
}
return answer.ToArray();
}
}
중복되는 소수를 다시 검사하지 않게 바꿔주었다.
맨 밑의 코드는 마지막 남은 수가 소수일 때 나눠지지 않지만 더해주어야하기 때문이다.
코딩테스트 : 컨트롤 제트
숫자와 "Z"가 공백으로 구분되어 담긴 문자열이 주어집니다. 문자열에 있는 숫자를 차례대로 더하려고 합니다. 이 때 "Z"가 나오면 바로 전에 더했던 숫자를 뺀다는 뜻입니다. 숫자와 "Z"로 이루어진 문자열 s가 주어질 때, 머쓱이가 구한 값을 return 하도록 solution 함수를 완성해보세요.
using System;
public class Solution
{
public int solution(string s)
{
int answer = 0;
string[] words = s.Split(' ');
for (int i = 0; i < words.Length; i++)
{
int preNumber = 0;
if (words[i] != "Z")
{
preNumber = int.Parse(words[i]);
answer += preNumber;
}
else
{
answer -= preNumber;
}
}
return answer;
}
}
using System;
public class Solution
{
public int solution(string s)
{
int answer = 0;
string[] words = s.Split(' ');
for (int i = 0; i < words.Length; i++)
{
int preNumber = 0;
if (words[i] != "Z")
{
preNumber = int.Parse(words[i]);
answer += preNumber;
}
else if (words[i] =="Z")
{
answer -= preNumber;
return 2;
}
}
return 1;
}
}
위 코드가 제대로 동작하지 않아 밑의 코드로 디버깅, 바르게 else로는 들어가나.. preNumber가 scope안에 있었다.
옮겨줘서 해결 ~
코딩테스트 : 숨어있는 숫자의 덧셈(2)
문자열 my_string이 매개변수로 주어집니다. my_string은 소문자, 대문자, 자연수로만 구성되어있습니다. my_string안의 자연수들의 합을 return하도록 solution 함수를 완성해주세요.
using System;
public class Solution
{
public int solution(string my_string)
{
int answer = 0;
string temp = "";
//string[] numbers = new string[]{"1","2","3","4","5","6","7","8","9","0" };
// char[] numbers = new char[] { '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' };
foreach (char c in my_string)
{
if (char.IsDigit(c))
{
temp += c;
}
else
{
if(temp !="")
{
answer += Int32.Parse(temp);
temp = "";
}
}
}
return answer;
}
}
IsDigit라는 것을 모르고 있다가 알게 되어서 사용함, 원래는 위의 numbers로 하나하나 비교해줬음
또 스코프 문제로 temp를 foreach문 안에 넣어두었다가 한참 고민함.
논리 자체는 어렵지 않았음.
다만 이 방식이면 마지막에 숫자가 아닌 문자가 나오지 않으면 마지막 숫자는 더해지지 않음.
if (temp != "")
{
answer += Int32.Parse(temp);
}
추가로 해결 !
코딩테스트 : A로 B만들기
문자열 before와 after가 매개변수로 주어질 때, before의 순서를 바꾸어 after를 만들 수 있으면 1을, 만들 수 없으면 0을 return 하도록 solution 함수를 완성해보세요.
using System;
using System.Collections.Generic;
public class Solution {
public int solution(string before, string after)
{
int answer = 0;
List<char> beforechar = new List<char>();
foreach (char c in before)
{
beforechar.Add(c);
}
foreach (char c in after)
{
if (beforechar.Contains(c))
{
beforechar.Remove(c);
}
else
{
return 0;
}
}
return 1;
}
}
https://galam.tistory.com-소제목 1-2
이렇게 없어졌으면 사라지게해서 오브젝트풀을 고려함.
eventSystem을 활용한 콜백시스템을 활용해서 싱글톤한테 InArea를 몰라도 들어왔는지 알려주려고 함 .
using System;
using System.Collections.Generic;
public class Message<T>
{
public T Data { get; private set; }
public Message(T data)
{
Data = data;
}
}
public interface IMessageQueue
{
void ProcessMessages();
}
public class TypedQueue<T> : IMessageQueue
{
private Queue<Message<T>> queue = new Queue<Message<T>>();
private List<Action<T>> handlers = new List<Action<T>>();
public void Enqueue(T data)
{
queue.Enqueue(new Message<T>(data));
}
public void AddHandler(Action<T> handler)
{
handlers.Add(handler);
}
public void RemoveHandler(Action<T> handler)
{
handlers.Remove(handler);
}
public void ProcessMessages()
{
while (queue.Count > 0)
{
var message = queue.Dequeue();
foreach (var handler in handlers)
{
handler?.Invoke(message.Data);
}
}
}
}
public class EventManager : Singleton<EventManager>
{
private Dictionary<Type, IMessageQueue> messageQueues = new Dictionary<Type, IMessageQueue>();
public void PushMessage<T>(T data)
{
GetQueue<T>().Enqueue(data);
}
public void Subscribe<T>(Action<T> handler)
{
GetQueue<T>().AddHandler(handler);
}
public void Unsubscribe<T>(Action<T> handler)
{
GetQueue<T>().RemoveHandler(handler);
}
private TypedQueue<T> GetQueue<T>()
{
var type = typeof(T);
if (!messageQueues.ContainsKey(type))
{
messageQueues[type] = new TypedQueue<T>();
}
return messageQueues[type] as TypedQueue<T>;
}
private void Update()
{
foreach (var queue in messageQueues.Values)
{
queue.ProcessMessages();
}
}
}
eventSysetm monsterManger 등등에 따로 바인딩을 하지 않아도 알아서 작동함 - 1시까지 커밋
단발성 이벤트들은 이렇게 관리하는 것이 좋다고 생각하심.
Update에서 사용되기 때문에 '즉시'는 아니지만 1프레임정도는 상관 없다 하면 사용하기 좋은 코드
이 Event 짠 다음 몬스터매니저 코드를 짜셨음
몬스터 시작하면 자기 자신 등록
서브스카리아브 해서 신호가 온다면 받을 리스너로 등록(몬스터라면) 플레이라면
몬스터들한테 디텍트되었어라고 알려주는 코드
void Start()
{
// 1번방식
EventManagerJ.Instance.Subscribe((MessageTypeNotifyInCountArea areaMsg) =>
{
EntityJ entityj = areaMsg.other.GetComponent<EntityJ>();
if (entityj is MonsterJ j1)
{
if (!monsterInCountArea.ContainsKey(areaMsg.InCountArea))
{
monsterInCountArea[areaMsg.InCountArea] = new List<MonsterJ>();
}
monsterInCountArea[areaMsg.InCountArea].Add(j1);
}
else if (entityj is PlayerJ j)
{
if (monsterInCountArea.TryGetValue(areaMsg.InCountArea, value: out var value))
{
foreach (var monsterJ in value)
{
monsterJ.OnDetectPlayer(j);
}
}
}
});
}
진행하던 중 Delegate와 Action에 대한 이해가 너무 없어서 코드를 이해하기 힘들었다.
if문 안에는 이해가 되지만, => 부분이 이해가 가지 않아 다시 개념을 공부해야겠다고 생각했다.
델리게이트 개념을 다시 공부했다.
https://febelo0524.tistory.com/64
델리게이트, 람다, Action,Func
Delegate함수에 대한 참조함수를 대신 호출함"타입"이다.(참조형 타입) // 즉 반환 타입과 매개변수로 넣을 수 있다.타입이므로 변수도 선언 가능하다그 자체로 콜백 기능을 해 준다. ( 함수를 먼
febelo0524.tistory.com
public class EventManager : MonoBehaviour
{
private Dictionary<Type, IMessageQueue> messageQueues = new Dictionary<Type, IMessageQueue>();
}
굳이 인터페이스를 만들어서 상속시킨 이유는, 여기서 TypedQueue<T>를 그대로 쓰면 EventManger<T>를 해야 하는데
그렇다면 타입이 고정되어 버리기 때문이다.
다시 만들었다.
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
// 메세지라는 클래스로 전달할 것. 이 메세지는 T, 즉 모든 데이터를 다 활용할 수 있는 클래스임.
public class Message<T>
{
public T Data { get; private set; }
public Message(T data)
{
Data = data;
}
}
public interface IMessageQueue
{
void DeliverMessage();
}
public class TypedQueue<T> : IMessageQueue
{
private Queue<Message<T>> messages = new Queue<Message<T>>();
private List<Action<T>> handlers = new List<Action<T>>();
public void Enqueue(T data)
{
messages.Enqueue(new Message<T>(data));
}
public void Dequeue(T data)
{
messages.Dequeue();
}
public void AddHandler(Action<T> handler)
{
handlers.Add(handler);
}
public void RemoveHandler(Action<T> handler)
{
handlers.Remove(handler);
}
public void DeliverMessage()
{
while (messages.Count > 0)
{
Message<T> message = messages.Dequeue();
foreach (Action<T> handler in handlers)
{
handler?.Invoke(message.Data);
}
}
}
}
public class EventManager : MonoBehaviour
{
private Dictionary<Type, IMessageQueue> messageQueues = new Dictionary<Type, IMessageQueue>();
private TypedQueue<T> GetMessageQueue<T>()
{
var type = typeof(T);
if (!messageQueues.ContainsKey(type))
{
messageQueues[type] = new TypedQueue<T>();
}
return messageQueues[type] as TypedQueue<T>;
}
public void PushMessage<T>(T message)
{
GetMessageQueue<T>().Enqueue(message);
}
public void Subscribe<T>(Action<T> handler)
{
GetMessageQueue<T>().AddHandler(handler);
}
public void Unsubscribe<T>(Action<T> handler)
{
GetMessageQueue<T>().RemoveHandler(handler);
}
public void Update()
{
foreach (var message in messageQueues.Values)
{
message.DeliverMessage();
}
}
}
의문 1 : TypedQueue는 istance로 계속해서 생성되는 것인가 ?
자연스럽게 생성된다고 한다. 생각해보면 new TypedQueue<T>가 생성되는 것을 알 수 있었는데
뭔가 객체처럼 보여야 생성되는것 아닌가? 하는 이상한 생각이 들었었나보다. 졸린가 ?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DetectingMessage
{
}
public class DetectingArea : MonoBehaviour
{
void OnTriggerEnter(Collider other)
{
var detecting = new DetectingMessage();
EventManager.Instance.PushMessage(detecting);
}
}
여기서 메세지만 전해주면 되는것 아닌가 ? 결국 Type으로 소통하고 있으니 class로 타입만 만들어주고
그 타입으로 메세지만 전달하고 메세지가 전달되면 상태를 바꾸려고 했었다.
그런데 이제 player 혹은 Capsule이 들어왔을 때만 상태가 바뀌게 하려면 메세지에 데이터를
집어넣어주면 좋을것 같았다.
생각해보니 확장을 위해서는 좋을 수도 있지만, 나는 지금 아니기 때문에 !
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DetectingMessage
{
}
public class DetectingArea : MonoBehaviour
{
void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Capsule"))
{
Debug.Log(other.name);
var detecting = new DetectingMessage();
EventManager.Instance.PushMessage(detecting);
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MonsterManager : Singleton<MonsterManager>
{
Dictionary<int, Monster> monsters = new Dictionary<int, Monster>();
void Start()
{
EventManager.Instance.Subscribe((DetectingMessage detectingMessage) =>
{
foreach (var monster in monsters.Values)
{
monster.OnDetectPlayer();
} });
}
public void AddMonster(Monster monster)
{
monsters.Add(monster.GetInstanceID(), monster);
}
public void RemoveMonster(Monster monster)
{
monsters.Remove(monster.GetInstanceID());
}
}
public void OnDetectPlayer()
{
Debug.Log("OnDetectPlayer");
stateMachine.ChangeState(typeof(ChaseState_Monster));
}
이렇게만 하고, 체이스 스테이트에선 그냥 Debug만 해줬다.
오늘도 이겼다
무슨 문제인지 모르겠지만 잘 동작하던 캡슐이 동작이 멈췄긴 한데 일단 한 잔 해~
아마 같은 StateMachine을 사용하다 보니 생기는 문제 같다.
Debug를 찍어보니 상태가 처음에 들어가지 않고 있다.
일단 잠을 좀 자야겠다.