https://www.youtube.com/watch?v=ETDgyFRyT_8&list=PLWKwcHKTXy5T5v_qSsvUnjFZG85pDOZPq&index=2

[Tesselator 단계]

테셀레이터는 3단계로 구성되어 있다.

HullShader → Tesselation → DomainShader 단계를 거친다.

 

Hullshader는 테셀레이터 작업의 첫단계

->VertexShader에서 공간변환을 진행하지 않고 Hullshader 정점정보들을 전달

Hullshader 는 폴리곤을 어떻게 분활 할 것인가? 폴리곤을 얼마나 분할 할 것인가? 를 결정하는 단계

조금 더 사실적으로 표현하기 위한 방법- 정점의 개수를 늘림

[적용전]

[적용후]

테셀레이터는 다시 말해 다각형을 겹치지 않고 작게 만들어 빈틈을 없애 게임등에서 사물이나 인물등을 실제에 보다 가깝게 표현할수 있게 도와주는 기술

 

DomainShader는 테셀레이터가 출력한 정점마다 한 번씩 함수(셰이딩 언어) 호출을 해주게 된다.

테셀레이션이 활성화 되면 기존의 정점쉐이더에서 수행한 것들을 도메인 셰이더에서 수행하게 된다.

예를들어 공간변환(월드 → 뷰포트 → 투상)이 될수 있다.

 

실제 게임을 제작 할떄는 정적이 적은 로우폴리곤과 정점이 많은 하이폴리곤 모델 두개를 따로 지원하는 경우 많다.

-> 자주 사용되는 기술은 아님.

여러가지 매쉬

[Geometry Shader]- 필수는 x

기본 폴리곤에서 정점을 추가하거나 또는 삭제 하거나 하는 연산을 할수 있다.

정점 정보를 추가하여 표현 할수 있는 모델이라면 그만큼의 정점정보를 뺴고 저장가능

-> 메모리적으로 용량을 적게 차지 할수도 있고 GPU 도움을 받아서 정점을 추가해주기 때문에 연산속도가 빨라 질수 있다.

같은 모델 2개 - 지오메트리 shader로 복사해오기
파티클 이벤트- 하나만 올려두고 지오메트리로 색, 위치바꾸기

[Rasterization]

정점처리 단계를 지난 정점은 다음 단계인 레스터라이저 단계로 넘어갑니다.

정점 - 삼각형 - 하나의 독자적 도형으로 처리된다.

우선 화면에 그려질 2차원 삼각형의 세 정점이 결정되면 다음과 같은 일이 일어납니다.

레스터라이저

  1. 이 삼각형이 포함하는 모든 픽셀마다 pixelShader(fragment shader-opengl)가 실행
  2. 삼각형의 세 정점에 할당 되었던 여러 데이터(pos, uv, normal, color)가 보간되어 삼각형 내부에 각 픽셀셰이더로 넘어옵니다.

정점 - 도형을 이루는 기본 - 나머지는 보간 

Directx에서는 이러한 과정을 통틀어서 레스터라이제이션

-> 고정 파이프라인 단계로 프로그래머 이러한 로직들을 임의 바꿀수 없는 파이프라인 단계

자체 알고리즘으로 알아서 동작을 한다.

대표적 레스터라이제이션의 역할을 나열해 보자면

  1. 클리핑
  2. 원근 나눗셈(perpective division)
  3. 후면 제거
  4. 스캔변환 
  5. 뷰포트 변환

[클리핑]

투영변환 이후의 클립공간 볼륨 바깥에 놓인 폴리곤들을 잘라내는 작업

가시부피안에 있는 것만 보이게

[원근 나눗셈]

현재 단계에서 투영변환을 통해 원근법이 적용된 3차원 물체들을 직육면체 클리핑 공간에서 정의되어 있다.

단순히 생각하면 3차원에서 2차원으로 차원을 줄이면 된다. 

동차좌표계 -> 일반좌표계

바로 Z좌표로 모든 성분을 나붜버리는거죠. 투영변환을 마친 정점데이터는 (x, y, z, w)에서 w성분에 z값이 저장된다. 원근 나눗셈이 적용 된 이후에는 x,y,z,w -> x,y,z의 좌표계로 변환되는데 이를 NDC(normailize device coordinate) 공간(동차좌표계 ->일반 좌표계 - 정규화 기기 좌표계 )이라고 부릅니다. 여기서 정규화라는 이름이 붙는 이 좌표의 xy 범위는 [-1 ~ 1] z의 범위는 [0~1]이기 때문

[후면 제거]

카메라가 바라보고 있는 방향에 물체에 가려진 면적은 굳이 연산을 할 필요가 없다.

외적(Cross product) 삼각형의 바라보고있는 면의 방향을 구하여 뒷면일 경우에 연산에서 제외 시킨다.

[뷰포트 변환]

컴퓨터 화면상의 윈도우 스크린 공간을 갖는데 이 스크린 공간 내에 2차원 이미지가 그려질 뷰포트가 정의되는데

NDC공간의 물체들을 스크린 공간으로 이전시키는변환을 뷰포트 변환

*[스캔 변환]

이전의 변환들은 자세한 사항을 몰라도 프로그래밍하는데 문제가 없었지만 이 스캔 변환은 렌더링 프로그램에서 직접적인 영향을 미치기 떄문에 꽤 중요하다.

삼각형 하나가 내부에 차지하는 모든픽셀(fragment)들을 생성하는 작업이다.

이때 정점데이터에 들어온 데이터들은 보간(선형 보간)되어서 픽셀셰이더로 넘어간다.

[Pixel Shader]

레스터화된 도형에 원하는 색을 입혀서 출력하게끔 도와주는 셰이더

텍스처매핑 , 노말매핑, 등등 기법으로 색을 입혀서 표현도 가능하다.

조명 처리나 이미지 처리를 할 때 유용하게 사용된다.

정점 데이터가 보간된 값이 넘어온다.

[Output merger]-알파테스트, 깊이테스트

깊이 - 스텐실 테스트와 블렌딩이 일어나서 최종적인 화면(텍스처)에 물체를 그려준다.

 

어느게 앞에 있는지에 따라 색이 달라질 것이다.
투명하다면 뒤에 색과 합쳐서

[Compute shader]- 부수적인 단계

병렬연산지원

일반 렌더링 파이프라인과 별도로 그래픽카드를 사용할때 실행할수 있도록 도와주는 셰이더

대량 병렬 GPGPU 알고리즘 또는 게임 렌더링의 일부를 가속시키기 위해서 사용 가능

효율적으로 사용 하려면 GPU 아키텍처와 병렬 알고리즘에 대한 지식뿐만 아니라 DirectxComput, Opengl Compute, CUDA, 또는 OpenCL에 대한 지식도 필요

'게임공부 > Directx11' 카테고리의 다른 글

[Directx11]4. 삼각형 그리기  (1) 2024.07.01
[Directx11]3.장치초기화  (0) 2024.06.27
[Directx11]2.초기설정  (0) 2024.06.26
[Directx11]그래픽스OT  (0) 2024.06.25
[Directx11]1. 렌더링 파이프라인-1  (0) 2024.06.19

https://school.programmers.co.kr/learn/courses/30/lessons/172927

 

처음에는 다이아 - 철 - 돌 순이여서 그냥 순회하면서 캐면 되는 건줄 알았는데

곡괭이마다 다른 광석에 대한 피로도 계산을 해줘야 하기때문에 DFS로 하는게 맞다고 한다. 

DFS를 통해 좋은 곡괭이로 캘 수 있는 모든 광석을 캐주고 다음 곡괭이로 넘어가면 된다.

#include <string>
#include <vector>
#include <map>
#include <algorithm>

using namespace std;
map<string, int> m; 
int fatigue[3][3] = {{1,1,1}, {5,1,1}, {25,5,1}}; // [곡괭이로][해당 광물 캘 때] = 드는 피로도
//하나의 곡괭이로 계속해서 캐야함 => DFS
void DFS(vector<int> &picks, vector<string> &minerals, int &answer, int sum, int location) {
    // 광물을 다 캤거나 곡괭이를 다 썼을 때 피로도를 갱신
    if (location == minerals.size() || (picks[0] == 0 && picks[1] == 0 && picks[2] == 0)) {
        answer = min(answer, sum);
        return;
    }
    
    // 0~2 곡괭이 방문
    for (int i = 0; i < 3; i++) {
        // i 곡괭이가 있다면
        if (picks[i] != 0) {
            picks[i]--;
            
            int tmpIdx = location; //캐야 할 광물의 현재위치
            int tmpSum = sum; // 광물을 캐며 누적된 피로도
            for (; tmpIdx < location + 5 && tmpIdx < minerals.size(); tmpIdx++) { // 5개를 캐거나 끝까지 캘 때까지
                tmpSum += fatigue[i][m[minerals[tmpIdx]]]; // 계속 광물을 캐기 피로도 누적
            }
            
            DFS(picks, minerals, answer, tmpSum, tmpIdx);
            
            picks[i]++;
        }
    }
}

int solution(vector<int> picks, vector<string> minerals) {
    int answer = (25 * 50) + 1; // 최고 피로도 * 최대 광물 개수
    
    m.insert({"diamond", 0});
    m.insert({"iron", 1});
    m.insert({"stone", 2});
    
    DFS(picks, minerals, answer, 0, 0);
    
    return answer;
}

https://www.youtube.com/watch?v=Wry5ltdrDQI&list=PLWKwcHKTXy5T5v_qSsvUnjFZG85pDOZPq&index=1

다이렉스x11 강의

파이프라인

여러 명령어가 중첩되어서 프로그램이나 하나의 작업을 실행하게 도와주는 과정(연산들의 집합)이다. 한 사이클 안에 들어가야 해서 과정들이 많기 때문에 한사이클이 복잡해지거나 사용자 입장에서 어려워질수 있는 단점이 존재한다.

렌더링 파이프라인(레스터라이제이션)

파이프라인

엔진을 먼저 학습하고 관련된 파이프라인을 학습해 나가자

 GPU

ALU가 CPU보다 많다 - 작은 데이터를 동시에 처리하기 좋다 - 무수히 많은 점 처리하기에 좋음 - RGBA(각 32bit)

[Input Assembler]

3차원 모델 하나를 3차원 세상에 나타내기 위해서는 가장 먼저 해줘야 할것은 무언인가?

여기서 모델은 점(vertex)로 이루어져 있다. 이것을 우리는 폴리곤(점들의 집합)이라고 한다.

주로 게임에서는 삼각형을 가지고 3D 폴리곤을 정의하는데, 이떄 이 정점데이터들을 운반하는 자료구조를 우리는 Vertex Buffer라고 한다.

정점(RAM)->GPU(Vertex Buffer) 

정점 버퍼와 같이 등장하는 용어로 Index buffer라는 것이 있다.

인덱스 버퍼는 쉽게 생각하면 정점들의 인덱스를 저장하고 있는 버퍼라고 할수 있는데

6개 정점활용

 

사각형을 예를 들면 사각형을 (삼각형 기반으로) 그리기 위해서는 2*3 = 6개의 정점이 필요하다.

사각형을 구성하는 정점을 4개만 두고 4개를 한 붓그리기 처럼 중복해서 그려주는 방식을 사용하는 걸 인덱스 버퍼의 기능이라고 생각할 수 있다.(하지만 없어도 상관은 없다)

한붓그리기

 

추가적으로 인덱스 버퍼를 사용하는데, 그것도 결국 메모리를 사용하니까 똑같다고 생각 할 수도 있다.

인덱스 버퍼 - 4바이트 숫자써서 효율적일 수 있다

정점은 위치데이터 말고도, 색깔, 법선, 텍스처좌표(UV), 애니메이션에 필요한 정보등등 여러가지 프로그래머가 원하는 데이터를 추가하여 사용하기 떄문에 단순히 정수만 저장하는 인덱스버퍼가 훨씬 메모리적으로 효율적이다.

인덱스 버퍼

정점버퍼는 그냥 정점들의 연속적인 메모리 구조에 불과 하기때문에 실제로 GPU에서는 이러한 정점들을 이용하여 어떤 도형을 만들어야 하는지 정보가 필요하다. 해당 도형정보를 Primitve Topology 라고 한다.

결국은 점을 어떻게 묶을 것인지 정하는것이다.

Primitve Topology

결론적으로 Input Assembler 는 이러한 정점들의 데이터를 읽고, 삼각형과 같은 도형으로 조립하는 단계의 일을 한다고 생각하면된다.

게임 - 무수히 많은 오브젝트

[Vertex Shader]

Input Assembler 에서 받은 정점정보들의 정보로 도형은 생성이 되었지만 로컬 좌표계에 있기 때문에

해당 데이터들을 화면에 그대로 출력해버리면 화면에 중심부에 전부 그대로 출력되어버려서 공간좌표계(World)로 변환할 필요가 있습니다. Local space에서 World space 로 변환이 필요하고,

실제 플레이어가 바라보는 카메라가 중심이 되는 공간 View Spcae(뷰포트변환)변환을 해준다. 그리고 마지막으로 Projection 변환(투상변환)을 거쳐 최종적으로 정의된 ClipSpace라는 공간으로 변환을 해준다.

옛날게임- 카메라라는 개념이 없었음(바이오하자드2)

최초카메라-마리오64

[월드공간 변환]

Local Space라고도 불리는 오브젝트 공간은 3차원 세상에서 효현될 각각의 개인의 공간에 정의된 영역이다.

Model Space
World Space

[카메라 공간 변환(뷰포트 변환)]

월드 변환이 완료되어 모든 물체가 한공간(World space)에 모아지면 이제 우리가 원하는, 시점에서 물체를 관찰 할 수 있게 해줘야한다.

이 때 관찰자로써 가상의 카메라가 필요하고, 이 카메라가 볼 수 있는 영역의 공간을 뷰 공간(뷰포트)이라고 합니다.

월드 공간의 모든 물체를 카메라 공간으로 변화하게 된다면 효율적으로 여러가지 효과나 렌더링등을 진행할수 있다.

뷰포트변환

가상의 카메라는 컴퓨터의 성능의 한계 때문에 실제 세상과는 다르게 시야가 제한 될수 있다.

FOV(시야각), ASPECT(종횡비)에 의해 결정 -> 가시공간 , 가시부피

이렇게 생성된 뷰볼륨에 Near(전방 절단면), Far(후방 절단면)정보가 전달되어 절두체의 영역을 다시 정의 한다.

가시부피

[View Frustrum]

절두체 공간 밖에 있는 물체는 그리지 않는데, 우리가 살고 있는 3차원 세상은 모든걸 보여준다. 이렇게 밖에 없는 이유는 계산상의 효율성을 위해 어쩔수 없이 도입된 개념

만약 물체가 절두체의 경계를 걸치게 되면 바깥쪽 부분은 잘려서 버리게 된다.->클리핑(Clipping)

이 클리핑은 카메라변환에서 이뤄지지 않고 나중에 클립공간에서 ->레스터라이저로 넘겨질때 수행

[투영변환]

투영변환

원근법을 활용 -> 원근변환

원근감(Depth Feeling)

동일한 크기의 물체라도 시점으로부터 멀리 있는 것은 작게 보이고 가까운 것은 크게 보임

●소실점(VP: Vanishing Point)

원근투상 결과 평행선이 만나는 점(시점 높이)

소실점의 수 -> 일점투상(One-point Projection), 이점투상(Two-point Projection), 삼점투상(Three-point Projection)

● 원근 변환(Perspective Transformation) 

직선->직선, 평면->평면

물체 정점 간의 거리에 대한 축소율이 달라짐(cf.어파인 변환)

투영 변환은 이러한 원근 법을 구현하기 위해 카메라 공간에서 정의된 절두체를 3차원 클립공간으로 변환하는것을 의미한다.

여기서 투영변환이라는 이름과는 다르게 3차원 공간의 물체를 2차원 평면으로 바꾸는것이 아니라 3차원 물체로 변형됨에 주목해야한다.

-> 아직 2차원평면이 아니다.

 

투영 변환을 거친 물체들을 관찰해보면 절두체 뒤쪽에 있던 영역의 폴리곤은 상대적으로 작아지는 것을 볼 수 있는데 우리가 원했던 원근법을 적용 된 것이라고 볼수 있다.

 

추가적으로 한개더 생각해 보면 원근법을 3차원 공간에서 실현하기 위해 직육면체 볼륨으로 물체들을 변환 시켰는데 여기서 얻는 이점이 있다. 좀 더 간단한 공식으로 쉽게 Clipping(클리핑)작업을 할 수 있다.

'게임공부 > Directx11' 카테고리의 다른 글

[Directx11]4. 삼각형 그리기  (1) 2024.07.01
[Directx11]3.장치초기화  (0) 2024.06.27
[Directx11]2.초기설정  (0) 2024.06.26
[Directx11]그래픽스OT  (0) 2024.06.25
[Directx11]2.랜더링 파이프라인2  (0) 2024.06.21

https://school.programmers.co.kr/learn/courses/30/lessons/62048

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

기하학과 수학적인 접근이 중요한 문제이다 

입출력 예 그림을 보면 가로는 8개만큼 겹치고 세로는 12개만큼 대각선에서 겹친다 

교차점은 중간에 보면 4개로 최대공약수 만큼 발생한다.

따라서 대각선에서 겹치는 부분에서 중복되는 교차점을 빼주면 되는 것이다.

중요한건 패턴을 찾고 수학적으로 기술하는게 중요할 것 같다.

#include <cmath>
using namespace std;
//대각선은 w개의 가로 격자선과 h개의 세로 격자선을 통과 따라서 W+H개의 경계선을 지나게 된다.
//교차점 최대공약수만큼 발생
//경계선- 중복된 교차점
int getGCD(long long w,long long h){
    int small,big;
    if(w>h){
        big=w;
        small=h;
    }else{
        big=h;
        small=w;
    }
    int mod=big%small;
    while(mod>0){
        big=small;
        small=mod;
        mod=big%small;
    }
    
    return small;
}

long long solution(int w,int h) {
    long long answer;
    long long W=w;
    long long H=h;
    
    int gcd=getGCD(W,H);  
    long long total=W*H;
    answer=total-(W+H-gcd);
    return answer;
}

https://school.programmers.co.kr/learn/courses/30/lessons/60057

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

1개부터해서 단위를 늘려가면서 압축했을 때의 문자열과 비교하면서 가장 짧은 문자열을 찾으면 된다.

중요한 점은 자르는 단위가 문자열의 절반까지가 최대값이고 

순회를 할때 반복되는 부분이 만약 2개 이상이 있다면 숫자를 붙여서 압축해주고 아니라면 그냥 문자열에 붙여서 압축 문자열을 만들어준다. 

 for(int i=1;i<=s.size()/2;i++){                //i는 문자열을 자르는 길이
        int cnt=1;                  //겹치는거 숫자
        string tmp="",a="";        //압축된 숫자, 자른 숫자
        a=s.substr(0,i);
        for(int j=i;j<s.size();j+=i){           //자른만큼 순회
            if(a==s.substr(j,i)) cnt++;             //만약 겹치는게 있다면 
            else{
                if(cnt>1) tmp+=to_string(cnt);
                tmp+=a;
                a=s.substr(j,i);                //다음에 검사할 부분
                cnt=1;                  //압축했으면 다시 겹치는거 찾기위해 1로 초기화
            }
        }

 

그리고 단위마다 압축문자열만들기 과정 후에 길이를 비교하고 짧은 것은 정답으로 저장해준다.

#include <string>
#include <vector>
//순회하면서 자르는 수를 증가시키면서 문자열 비교
using namespace std;
int solution(string s) {
    int answer = s.size();
    for(int i=1;i<=s.size()/2;i++){                //i는 문자열을 자르는 길이
        int cnt=1;                  //겹치는거 숫자
        string tmp="",a="";        //압축된 숫자, 자른 숫자
        a=s.substr(0,i);
        for(int j=i;j<s.size();j+=i){           //자른만큼 순회
            if(a==s.substr(j,i)) cnt++;             //만약 겹치는게 있다면 
            else{
                if(cnt>1) tmp+=to_string(cnt);
                tmp+=a;
                a=s.substr(j,i);                //다음에 검사할 부분
                cnt=1;                  //압축했으면 다시 겹치는거 찾기위해 1로 초기화
            }
        }
        if(cnt>1) tmp+=to_string(cnt);
        tmp+=a;
        if(answer>tmp.size())answer=tmp.size();   
    }
    return answer;
}

1. Manager 관련 코드에는 monobehavior은 안쓰는게 좋다.

-굳이 필요가 없다

2. UI요소들의 크기를 조정할 때는 Scale을 조정하기보다 Anchor와 width height로 조절하는게 맞다

- Scale은 곱연산으로 중첩되어 들어가기 때문에 각 요소들의 크기를 결정하기 쉽지 않지만 Anchor와 width, height를 조정하면 지정한 값으로 들어가기 때문에 훨씬 좋다.

3. 코드를 잘 읽고 ctrl + 12 + 함수 및 매서드 정의 잘 보기

 해당 함수가 사용된 곳을 찾으면서 어떻게 함수가 쓰이는지 어떤 방식으로 구현되어 있는 지 확인하고 이를 활용하여 자신만의 코드를 만드는게 효율적이다.

 

계속 업데이트 예정...

 

현재 남은 병사에서 만약 막을 수 있다면 막으면서 해당 값을 최대값으로 저장한다. 

그후 만약 저장된 값을 검사해서 

1.만약 기존 값이 크다면 전의 걸 무적권을 쓰고 해당 수만큼 병사를 돌려받는다.

2.만약 기존 값이 현재의 수보다 작을 때 막을 수 있으면 막고

3.아닐 때 저장된 큰 값이 없는 경우는 무적권을 쓰거나 게임오버

#include <string>
#include <vector>
#include <queue>

using namespace std;
//큰값 저장 및 검사
int solution(int n, int k, vector<int> enemy) {
    priority_queue<int> pq;
    int answer = 0;
    for(int i=0;i<enemy.size();i++){
        if(n>=enemy[i]){        //만약 현재가진 병사로 막을 수 있다면
            n-=enemy[i];
            pq.push(enemy[i]);      //최대적 수 저장
        }else{
            if(k>0){            //무적권이 남아있다면
               if(!pq.empty()&&pq.top()>enemy[i]){  //만약 전에 최대였던 수가 현재적보다 크다면
                   n+=pq.top();                              //무적권을 사용해서 돌려받기
                   pq.pop();
                   pq.push(enemy[i]);
                   n-=enemy[i];                 //전에 무적권을 쓰고 지금꺼는 병사로 막기
                }
                k--;
            }else
                return i;
        }
    }
    
    return enemy.size();
}

하노이의 탑은 유명한 재귀 문제이다.

매번 볼때마다 어렵긴하지만 그냥 외우는게 답일 것 같다.

한개만 남아있을 때는 옮겨야하는기둥에 바로 옮기면 되고 

n-1가 남아있을 때는 sub기둥에 n-1를 옮겨두면 된다.

그리고 sub기둥에 있는 것도 end로 하나씩 옮기면 된다.

핵심 코드 

  if(n==1){       //한개만 남았다면 from-> to
        v.push_back(start);
        v.push_back(end);
        answer.push_back(v);
    }else{
        //n-1개를 from -> sub로 옮기기
        hanoi(n-1,start,sub,end,answer);
        
        //from-> to
        v.push_back(start);
        v.push_back(end);
        answer.push_back(v);
        //sub-> to
        hanoi(n-1,sub,end,start,answer);
    }

전체코드

#include <string>
#include <vector>

using namespace std;
void hanoi(int n,int start,int end,int sub,vector<vector<int>> &answer){
    
    vector<int> v;
    if(n==1){       //한개만 남았다면 from-> to
        v.push_back(start);
        v.push_back(end);
        answer.push_back(v);
    }else{
        //n-1개를 from -> sub로 옮기기
        hanoi(n-1,start,sub,end,answer);
        
        //from-> to
        v.push_back(start);
        v.push_back(end);
        answer.push_back(v);
        //sub-> to
        hanoi(n-1,sub,end,start,answer);
    }
    
    return;
}


vector<vector<int>> solution(int n) {
    vector<vector<int>> answer;
    hanoi(n,1,3,2,answer);
    
    return answer;
}

BFS문제이다.

BFS이지만 방향에 따라 장애물이나 끝에 도달할 때 까지 진행한다는 차이점이 있다. 

만약 G에 도달하면 time 최소시간 time을 반환해준다.

#include <string>
#include <vector>
#include <queue>
#define MAX 101
int w,h;
int dirX[4]={-1,1,0,0};
int dirY[4]={0,0,-1,1};
using namespace std;
int BFS(int i,int j,vector<string> board){
    queue<pair<pair<int,int>,int>> q;
    bool visited[MAX][MAX]={0,};
    int time=1e9;
    
    q.push({{i,j},0});
    visited[i][j]=true;
    
    while(!q.empty()){
        int curx = q.front().first.first;
        int cury = q.front().first.second;
        int cur_time = q.front().second;
        q.pop();
        if(board[curx][cury]=='G') time=min(time,cur_time);
        
       for (int i = 0; i < 4; i++) {
            int nx = curx + dirX[i];
            int ny = cury + dirY[i];

            if ((nx < 0 || nx >= w || ny < 0 || ny >= h)|| board[nx][ny]=='D') continue;           //못가는 곳은 넘어가기  
                
            while(true){            //장애물 있는데까지 전진
                nx+=dirX[i];
                ny+=dirY[i];
                
                if ((nx < 0 || nx >= w || ny < 0 || ny >= h)|| board[nx][ny]=='D'){
                    nx-=dirX[i];
                    ny-=dirY[i];                //장애물까지 갔으니 그전 좌표 계산
                    break;
                }
            }
            if(visited[nx][ny]) continue;
           
            q.push({ {nx,ny},cur_time + 1 });
            visited[nx][ny] = true;
        }
    }
    
    return time==1e9? -1: time;
}

int solution(vector<string> board) {
    int answer = 0;
    w=board.size();
    h=board[0].size();
    for(int i=0;i<w;i++){
        for(int j=0;j<h;j++){
            if(board[i][j]=='R'){
                answer=BFS(i,j,board);
            }
        }
    }
    return answer;
}

사람을 발견했을 때

BFS를 통해 탐색을 해서 만약 맨해튼 거리가 2이하인 사람이 있다면 false를 반환해주면 된다. 

이때 만약 탐색 중에 X  즉 파티션을 만나게 되면 파티션 너머는 탐색할 필요가 없어서 continue 처리를 해주면 된다.

그리고 만약 false가 하나라도 있다면 그 대기실은 거리두기를 지키지않는 것이여서 break;처리를 해주면 된다.

#include <string>
#include <vector>
#include <queue>
using namespace std;

int dx[4] = {-1, 1, 0, 0};  // 왼, 오, 아래, 위
int dy[4] = {0, 0, 1, -1};

bool check_dist(int i, int j, vector<string> place) {           //BFS
    bool visited[5][5] = {false};
    queue<pair<pair<int, int>, int>> q;
    visited[i][j] = true;
    q.push({{i, j}, 0});
    
    while (!q.empty()) {
        int cur_x = q.front().first.first;
        int cur_y = q.front().first.second;
        int dist = q.front().second;
        q.pop();
        
        for (int h = 0; h < 4; h++) {
            int nx = cur_x + dx[h];
            int ny = cur_y + dy[h];
            
            if (nx < 0 || nx >= 5 || ny < 0 || ny >= 5) continue;
            
            if (visited[nx][ny] || place[nx][ny] == 'X') continue;        //파티션이 있는 경우 너머는 탐색할 필요 없음  
            
            if (place[nx][ny] == 'P' && dist + 1 <= 2) {            //사람이 있는데 맨해튼거리가 지켜지지 않으면
                return false;
            }
            
            visited[nx][ny] = true;
            q.push({{nx, ny}, dist + 1});
        }
    }
    
    return true;
}

vector<int> solution(vector<vector<string>> places) {
    vector<int> answer;
    
    for (auto place : places) {
        bool check = true;
        
        for (int i = 0; i < 5 && check; i++) {
            for (int j = 0; j < 5; j++) {
                if (place[i][j] == 'P') {
                    if (!check_dist(i, j, place)) {
                        check = false;
                        break;
                    }
                }
            }
        }
        
        answer.push_back(check ? 1 : 0);
    }
  
    return answer;
}

+ Recent posts