확률 뽑기 로직 정리

C# 2022. 3. 3. 13:00
728x90

물건 5개 잇다 가정

차례대로 1,2,3,4,5번으로 지정하고

각각의 확률은 5%, 10%, 10%, 30%, 45%

누적확률을 이용하면

누적 확률은 5%, 15%, 25%, 55%, 100%

만약에 랜덤으로 뽑은 확률값이 70%가 나왔다. 그럼 누적확률 값을 사용해서 5%, 15%, 25%, 55%인 1번,2번,3번,4번 제외 100%인 5번이 선택되는것

여기서 주의할 점은 누적으로 더하는 확률값을 사용하기때문에 확률 순서거 적은것부터 정렬해서 계산되도록 해야한다.

    float[] probs = new float[5]{5.0f, 10.0f, 10.0f, 30.0f, 45.0f}; //1, 2, 3, 4, 5
    float totalProbs = 100.0f;
    float randomValue = Random.value * totalProbs;
   
    double cumulative = 0.0f;

    for(int i=0; i<5; i++)
    {
        culValue += probs[i];
        if(randomValue <= culValue)
        {
            return i;
        }
    }

반대로 누적으로 빼는 방법으로 하는 방법도 있음 

뺀다면 확률이 큰거부터 정렬해서 계산되도록 해야되는거 말곤 같은듯

아래 유니티 사이트에서 예제 코드를 확인할수있음

https://docs.unity3d.com/kr/2017.4/Manual/RandomNumbers.html

728x90
Posted by 정망스
,

서버 동기화 방식

C# 2021. 7. 23. 12:28
728x90

Peer-to-Peer Lockstep 방식(RTS)

▶ 모든 Peer들의 상태값을 항상 완벽하게 일치 시킨다.

동일한 input을 동일한 타이밍에 주면 모든 Peer들의 결과값은 동일하다.

 

구현

 unit의 상태값(hp,위치)을 동기화하지 않고, 유저의 input(유닛생산,유닛선택,유닛이동,유닛이동및공격)만 동기화 한다.

한 Peer의 input을 동일한 타이밍에 모든 Peer에게 전달하기 위해 input delay를 준다.

스타크래프트의 경우 200ms라고 한다.

200ms안에 모든 Peer들에게 input을 전달한다.

Peer가 100ms만에 전달 받았다면 100ms후에 받은 input을 수행하고, 50ms만에 받았다면 150ms후에 수행하는 식이다.

이 딜레이를 속이기 위해서 유닛을 이동시키면 당장 이동하지는 않지만 제자리에서 소리나 애니메이션을 바로 한다.

(ex. 뮤탈 : 제자리에서 날개짓 시작 및 소리를 낸다.)

 

장점

구현이 간단하다

모든 Peer의 상태가 동일하기 때문에, Server같은 신뢰할수 잇는 중간 매개체를 둬서 Peer들의 상태를 시뮬레이션 할 필요가 없다.

모든 Peer의 상태가 동일하기 때문에, Peer들간의 상태값 불일치를 자연스럽게 좁혀줄 보간 처리가 필요 없다.

 

단점

Peer의 모든 input이 모든 Peer에게 전달 됨으로서 맵핵이 나올수 밖에 없다.

게임에 랜덤요소가 없어야 하거나, 있더라도 초기 게임을 시작하기 전에 random함수의 seed값을 동일하게 맞춰야 한다.

모든 Peer의 상태를 동일하게 하기 위해서, 모든 Peer가 input을 줄때까지 기다려야 한다.

가장 안 좋은 Peer의 latency가 모든 Peer의 latency가 된다.

한 Peer라도 input을 주지 않으면 모든 Peer의 게임 진행이 멈춘다.

게임이 시작된 상태에서 중간에 진입할수 없다.

Server같은 신뢰 할수 있는 대상이 전투상태값을 시뮬레이션 하고 있지 않기 때문에 게임 중간에 상태값을 줄 수 없다.

 

Client/Server 방식(FPS, MMORPG)

모든 Client들의 상태값을 항상 완벽하게 일치 시키려고 하지 않는다.

 대신에 Client들 사이에 Server라는 신뢰할수 있는 모델을 두고, Client들의 상태값이 Server의 상태값에 수렴하도록 만든다.

 

구현

 Client는 Server에게 유저의 상태값(hp,위치)이 아닌 행동(스킬 사용, 어떤 방향으로 이동)을 알리고 Server는 해당 행동을 받아서 상태값을 시뮬레이션 한다.

 Server는 다른 Client들에게 유저의 행동뿐만 아니라 시뮬레이션한 상태값도 같이 알린다.

 Client가 반응성을 위해 선처리한 상태가 Server와 다를수 있음으로 상태값도 같이 보내서 Client가 보간할수 있게 한다.

반응성을 높이기 위해 다른 유저에게 당장 영향을 주지 않은 행동들은 Server의 응답을 기다리지 않고, Client에서 먼저 선처리한 후 Server의 결과를 받아서 보간한다.

 이동 : 이동 자체가 다른 유저에게 당장 영향을 주지는 않음으로 Client에서 선처리 하고 후에 보간한다.

 공격 : 다른 유저에게 당장 영향을 줄 수 있는 행동이라서 Client에서 선처리 하지 않고 Server에 요청후 응답을 기다렸다 처리한다.

칼을 들어 올리는 모션(input delay)동안 Server에 요청해서 다른 Client들에게 공격을 알린후, 공격에 대한 효과 적용은 Client들과 Server모두 같은 순간에 이루어 지도록 한다.

 

장점

 유저의 행동이 Server를 거쳐서 다른 Client에게 전달될때 제한된 정보만 전달해서 보안성을 높일수 있다.

 Server에서 랜덤요소를 결정해서 알려줄수 있다.

 Client중 하나가 응답하지 않는다고 해서 모든 다른 Client가 기다릴 필요가 없다.

 게임이 시작된 상태에서 중간에 진입할수 있다.

 

단점

 Server가 Client의 행동들을 다 시뮬레이션 해야 한다.

 Client가 Server와의 상태값 불일치를 어색하지 않게 좁혀줄 보간 처리를 잘 해야 한다.

728x90
Posted by 정망스
,
728x90
public static IEnumerable<AAA> GetData(AAAType type)
{
   var groupList = new List<int>();
   int categoryID = (int)type + 1;

   return Database.Shared.GetDatas<AAA>()
                                 .Where((data) =>
                                 {
                                     if (groupList.Contains(data.GroupID) || data.CategoryID != categoryID)
                                          return false;

                                         groupList.Add(data.GroupID);
                                          return true;
                                 })
                                 .OrderBy(data => data.GroupID);
}

이 코드에는 문제가 있다.

 

힌트는 클로저

디버그로 저 함수를 통해 받은 컬렉션의 갯수를 찍어보았다.

var aaa = Data.GetData(AAAType.A);

Debug.Log(aaa.Count());
Debug.Log(aaa.Count());

첫번째 : 30개
두번째 : 0개

???..

왜 두번째에서 0개가 나오는가 .... 

이유는 LINQ는 지연평가가 이루어 지므로 

Count()를 호출할때 평가를 하게 되는데 첫번재 Count()를 실행하여 GetData의 LINQ가 처리 될때는 groupList가 처음 생성되고 조건에 맞게 처음 Add가 되어 정상적으로 Count가 잘 나온것이였고

 

두번째 Count()를 호출할때는 groupList가 클로져로 사용되었고 캡쳐 되었다 보니 지워지지 않고 그대로 유지 된 채로 존재하고 groupList에는 이미 해당하는 데이터에 groupID가 들어있어서 위의 조건인

if (groupList.Contains(data.GroupID) || data.CategoryID != categoryID)
    return false;

에 의해 전부다 false로 팅겨내어서 아무런 데이터가 없는 채로 반환된것이였다..

 

인지하고 잘 쓰자 ... 

728x90
Posted by 정망스
,
728x90

직렬화(Serialization)

 

정의(definition)

직렬화(Serialization)는 메모리 내부의 오브젝트(object)나 오브젝트 그래프(object graph; 서로를 참조하는 오브젝트들의 집합)을 바이트(bytes)로 이루어진 스트림(stream)이나 XML 노드들로 만들어 보관되거나 전송될 수 있도록 변환하는 것을 말한다. 

역직렬화(Deserialization)는 반대로 데이터 스트림을 메모리 내부의 오브젝트나 오브젝트 그래프로 재구성하는 것을 말한다.

 

목적(purpose)

직렬화와 역직렬화는 주로 다음 두 목적으로 사용된다.

오브젝트를 네트워크나 어플리케이션 경계 상으로 전송하는 목적.

파일이나 데이터베이스로 오브젝트의 표현(representation)을 보관하는 목적. 

 

유니티에서의 직렬화

유니티(Unity)에서의 직렬화는 객체의 정보 은폐(Information Hiding)를 해치지 않으면서 

Inspector창을 통해 해당 변수를 입력 받는 용도로 사용한다.

728x90
Posted by 정망스
,
728x90

패턴 매칭이란 호출해야 할 함수의 변형을 정확하게 선택하는 디스패치의 유형이다

 

입력 값을 전달하고 정확한 선택을 결정하는 if 조건식의 개념으로 생각하면 되는것 같다.

 

c#의 Nullable<T>, IEnumerable<T>, Func<T>, Lazy<T>, Task<T> 형식은 모나드라는 개념을 구현하고 있다.

 

1. 모나드는 다른 타입을 인자로 받는 타입이다.

모나드는 기본적으로 int나 string 같은 타입이고, 타입을 인자로 받는다고 한다.

c++의 템플릿, c# 제네릭 클래스를 생각하면 되고, 이 T라는 타입의 인자를 받는 모나드 M을 M[T] 라고 표현을 한다.

return operator 혹은 unit operator라고 부른다.

 

2. 모나드는 타입의 값을 생성하는 함수가 있어야 한다

모나드는 임의 타입의 값을 받아서 그 타입을 인자로 받은 모나드 타입의 값을 반환하는 함수가 있어야 한다.

T 타입의 값을 받아서 M[T] 타입의 값을 반환하는 함수가 있어야 한다.

보통 bind operator라고 부른다.

 

3. 다른 모나드 타입으로 진행하는 함수가 있어야 한다.

M[T] 타입의 모나드가 있을 때 T타입의 변수를 받아 M[U] 타입의 모나드를 반환하는 함수를 받아서, M[U] 타입의 값을 반환하는 함수다.

이 함수를 통해서 모나드에서 다른 모나드로 진행 할 수 있다.

 

위 3가지 조건을 만족해야 모나드라는 패턴이 만족 된다고 한다. 

 

...........프로그래머 로써는 이정도로만 이해해도 된다고 하는데 그래도 이게 무슨 말인지 알수가 없구나...

 

사용 예제 : Option : 존재하지 않음을 표현하기 

(출처 : https://blog.seulgi.kim/2015/07/monad-option.html)

Option 타입이 가장 기본적인 모나드이고, 많이 사용되는 모나드라고 한다.

 

Option 타입이 해결하고자 하는 문제는 값이 존재하지 않음을 런타임 에러가 발생할 가능성 없이 표현하는 것이다.

 

C++, C# 등 기존의 많은 언어는 값이 존재하지 않음을 표현하기 위해서 null point를 사용하는데 이 null point는 컴파일 타임에 잡을 수 없는 nullpointerException을 발생 시키기 떄문에 조심해야 한다.

 

이런 문제를 null object pattern 같은 패턴을 이용하거나 null check를 한 겹 감싼 클래스를 만들거나 해서 보완하지만 문제를 완벽하게 해결할 수는 없다

 

하지만 Option 타입은 이에 대한 해결책을 제공한다.

 

Option 타입은 하나의 타입 파라미터를 받아, 그 타입의 값을 가지고 있을 수도 있고, 없을 수도 있다. Int 타입을 타입 파라미터로 받았다면, 타입은 Option[Int]가 되며, String 타입을 타입 파라미터로 받았다면, Option[String]이 된다. 즉, T 타입을 타입 파라미터로 받은 Option은 Option[T]가 된다. 이를 간단히 표현하기 위해서 T?같은 방식으로 표현하기도 한다.

Option[T] 타입의 값은 T 타입의 값을 가지고 있을 수도 있고, 아무런 값이 없을 수도 있다. 이렇게 말하면 단순한 nullable과 다를 게 없어 보인다. 하지만 Option은 두 상태를 다른 타입으로 분리함으로써 nullable보다 안전한 방법을 제공한다.

Option 타입은 두 타입의 sum type이다. 하나는 값이 존재하지 않음을 나타내는 None이라는 타입이고, 다른 하나는 무언가 값이 있음을 나타내는 Some이라는 타입이다. sum type을 지원하는 F#, rust, Haskell 같은 언어에서는 이를 sum type으로 표현하고, Scala나 전통적인 객체지향 언어에서는 Option이라는 interface의 구현체로 Some과 None이 있는 것으로 표현한다.

 
None은 아무런 값도 가지고 있지 않음을 나타내는 타입이다. None 타입의 값에 bind operator를 호출해도 아무 일도 일어나지 않는다. 인자로 넘겨진 함수는 실행되지 않고, bind operator의 결과는 언제나 None이다.
 
 Some 타입은 무언가 값을 가지고 있음을 나타내는 타입이다. 어떤 타입의 값을 가졌는지 나타내기 위해 타입 파라미터를 받는다. T 타입의 값을 가지고 있는 Some 타입은 Some[T]라고 표현한다. Some[T] 타입은 반드시 T 타입의 값을 들고 있어야 한다. Some 타입이면서 내부적으로 값을 들고 있지 않는 상황은 올 수 없다.1) Some타입은 반드시 값을 가지고 있기 때문에 bind operator는 언제나 NullPointerException 없이 원하는대로 실행된다.
 

책을 보면서도 처음 보는 개념이라 많이 이해하기 힘들고..

내가 이해한 것이 맞는건지는 모르겠지만. 정리를 해보자면

 

모나드가 바라는 것은 함수 A의 결과를 함수 B에 전달해서 수행을 하고 싶은데 

함수마다 정의된 리턴값 타입이 다를수도 있고, 

A의 결과를 특정 공간에 저장한 다음, 함수 B가 해당 공간에 접근해서 수행을 진행하는 방법이 있다고 한다면

이 방법은 A, B 함수가 아닌 또다른 외부 함수가 공간에 저장할 가능성이 있고 그렇다면 완벽한 결과를 보장할 수 없기 때문에 함수형 프로그래밍에서 원하는 방향과는 맞지 않는다는 것이고

이것을 해결하기 위해 함수 A와 B를 합친다? 라고 해야할까,, 하나의 함수인 것처럼 동작하게 하는 함수를 만든다는 것이 모나드의 개념인 거 같다.

 

추가 한줄 요약 정리 : <T> 타입의 Functor를 <R> 타입의 Functor로 바꾸는 기능

 

책에있는 간단한 예제로 보면

string타입을 받아서 string에 해당하는 지정된 숫자 int 타입을 반환하는 함수가 있다

private static Nullable<int> WordToNumber(string word)
{
  Nullable<int> returnValue;
  if ( word == null ) return null;
  switch (word.ToLower())
  {
    case "zero" : returnValue = 0; break;
    case "one" : returnValue = 1; break;
                     .....
    default: returnValue = null;
  }
int 형식은 null을 처리하지 못하지만, Nullable<int>를 사용해서 null을 반환 할수있게 한것이다.

 

WordToNumber에는 우리가 따로 if문을 사용해서 if (word == null) 이냐 인것처럼 null 체크가 불필요하다.


null일 경우에는 그냥 null을 반환하고 값이 null이면 로직이 실행이 되지 않는다거나 하게끔 로직을 짜서 타입에 대한 안정성을 유지 할 수 있다는 것


이런 예외적인 값에 대한 컨트롤이 가능한 로직을 짤수 있다는 방식 때문에
비동기 데이터를 다루는 로직에 있어서도 값이 이미 존재하는 것 처럼 로직을 짤 수 있을것 같다.


중간에 예외 발생 없이 함수형 프로그래밍에서 추구하는 입력에 따른 결과를 반환하는 것을 보장하는 방법이 되는 것이 모나드 패턴인거 같다.

 

혼자 프로그래밍 작업을 하는 환경이라면 모르겠지만.. 여러 사람들과 같이 하는 작업중에 이러한 패턴을 이용해서 함수를 설계 할때는 사전에 협의가 잘 되어 있어야 가능할 것 같고, 

합친다, 확장한다라는 개념은 물론 좋지만 이것도 결국엔 몸집이 계속해서 커져 나간다면 .. 어떻게 관리되어지고 어떻게 유지 보수를 해 나갈지, 이런 걱정도 많이 해보게 될 것 같다. 

 

 

728x90
Posted by 정망스
,


맨 위로
홈으로 ▲위로 ▼아래로 ♥댓글쓰기 새로고침