Carrot
본문 바로가기
Unity/멋쟁이사자처럼 부트캠프

[멋쟁이사자처럼 - 유니티 스튜디오 인턴십] Level Editor 수정

by 독기품은토끼 2025. 12. 4.

1. Level Editor 수정

연산 시간이 너무 오래 걸려서 검증이 제대로 안되고 있는 문제가 있어서

방법론을 바꿔서 구현하기로 하였다.

 

지금은 한번에 모든 오리를 다 본 후 성공 로직이 있는 경우의 수를 뽑고 있는데

이러니 너무 depth가 깊어져서 결국은

1. 한번에 모든 오리를 다 보지 않고

2. 오리 한마리씩 단계적으로 해결해서

3. 해결된 시점의 GameState를 새 root로 잡아서

4. 다시 다음 오리 해결을 반복

이런식으로 방법론을 구성하였다.

 

여기서 추가로 넣어주면 좋은 기능은

1. 해결된 오리, 파이프, 골은 Id 부여해줄 것 (인간이 직접 부여하는 불필요함 ↓)

 


 

public bool IsGameComplete()
{
    // 모든 오리가 목적지에 도달했거나 제거되었는지 확인
    return Ducks.Count == 0 || Ducks.All(d => d.IsCompleted);
}

 

지금은 모든 오리 다 없어졌을 때에만 성공하기 때문에 우선 이부분부터 고쳐주자

 

private static async Task<(List<List<WaterSlideSimulator.GameAction>> solutionPaths, int exploredStates)> 
    FindSolutionsOptimizedAsync(
        WaterSlideSimulator simulator,
        WaterSlideSimulator.GameState initialState, 
        int movableTileCount,
        AdvancedValidationResult result,
        CancellationToken cancellationToken,
        IProgress<float> progress,
        Func<WaterSlideSimulator.GameState, bool> isGoal   // 추가
    )
    
    if (isGoal == null)
        isGoal = s => s.IsGameComplete(); // 맨 윗줄 추가
        
    var solutionPaths = new List<List<WaterSlideSimulator.GameAction>>();
    ...
// 초기 상태가 이미 완료 상태인지 먼저 체크
if (initialState.IsGameComplete())
{
    solutionPaths.Add(new List<WaterSlideSimulator.GameAction>()); // 액션 없이 완료
    return (solutionPaths, 1);
}

// 아래처럼 변경
// 초기 상태가 이미 목표를 만족하는지 체크
if (isGoal(initialState))
{
    solutionPaths.Add(new List<WaterSlideSimulator.GameAction>());
    return (solutionPaths, 1);
}

while (...)
{
    ...

    // 기존: if (currentNode.State.IsGameComplete())
    if (isGoal(currentNode.State))
    {
        solutionPaths.Add(currentNode.Path);
        ...
        continue;
    }

    ...
}
// 한 단계: 현재 상태에서 Ducks.Count 를 줄이는 경로를 찾고, 그 결과 상태를 반환
private static async Task<(bool success,
                           List<WaterSlideSimulator.GameAction> stepPath,
                           WaterSlideSimulator.GameState nextState,
                           int explored)>
    SolveOneDuckStepAsync(
        WaterSlideSimulator simulator,
        WaterSlideSimulator.GameState rootState,
        int movableTileCount,
        AdvancedValidationResult result,
        CancellationToken token,
        IProgress<float> progress)
{
    var startDuckCount = rootState.Ducks.Count;

    // 이미 오리가 없으면 더 할 게 없음
    if (startDuckCount == 0)
        return (true, new List<WaterSlideSimulator.GameAction>(), rootState, 0);

    // 이번 단계의 목표: "오리 수가 줄어든 상태"
    Func<WaterSlideSimulator.GameState, bool> isGoal =
        s => s.Ducks.Count < startDuckCount;

    // 기존 검색 함수를, 우리가 넣은 isGoal 로 호출
    var (paths, exploredStates) = await FindSolutionsOptimizedAsync(
        simulator,
        rootState,
        movableTileCount,
        result,
        token,
        progress,
        isGoal);

    if (paths == null || paths.Count == 0)
    {
        // 이 루트에서는 오리 수를 줄일 방법이 없음 = 막다른 길
        return (false, null, rootState, exploredStates);
    }

    var stepPath = paths[0];

    // stepPath 를 실제로 적용해서 다음 단계 루트 상태 만들기
    var current = rootState;
    foreach (var action in stepPath)
    {
        current = simulator.ApplyActionAndSimulate(current, action);
    }

    return (true, stepPath, current, exploredStates);
}
// 전체 게임: 오리가 0 마리가 될 때까지 SolveOneDuckStepAsync 를 반복
private static async Task<(List<List<WaterSlideSimulator.GameAction>> solutionPaths, int exploredStates)>
    SolveAllDucksStepByStepAsync(
        WaterSlideSimulator simulator,
        WaterSlideSimulator.GameState initialState,
        int movableTileCount,
        AdvancedValidationResult result,
        CancellationToken token,
        IProgress<float> progress)
{
    var currentState = initialState;
    var fullPath = new List<WaterSlideSimulator.GameAction>();
    var totalExplored = 0;

    // 안전장치: 최대 단계 수
    var maxSteps = initialState.Ducks.Count + 5;

    int step = 0;
    while (currentState.Ducks.Count > 0 && step < maxSteps)
    {
        step++;

        var (ok, stepPath, nextState, explored) = await SolveOneDuckStepAsync(
            simulator,
            currentState,
            movableTileCount,
            result,
            token,
            progress);

        totalExplored += explored;

        if (!ok)
        {
            // 이 순서/루트로는 중간에 막다른 길 → 해답 없음
            return (new List<List<WaterSlideSimulator.GameAction>>(), totalExplored);
        }

        fullPath.AddRange(stepPath);
        currentState = nextState;
    }

    if (currentState.Ducks.Count == 0)
    {
        // 한 개의 전체 경로를 솔루션 리스트로 감싸서 반환
        var list = new List<List<WaterSlideSimulator.GameAction>> { fullPath };
        return (list, totalExplored);
    }
    else
    {
        return (new List<List<WaterSlideSimulator.GameAction>>(), totalExplored);
    }
}