부품 설명서

[C#] 문자열로 조작 가능! 대화창 만들기!

밤말팅 2021. 1. 30. 19:21

문자열을 대화창에 재생해주는 기능을 만듭시다!

안녕하십니까. 밤말팅입니다!

 

저번 시간에는 [은/는] 과 같이

앞 글자에 영향을 받는 조사

변환시켜 보았는데요!

 

 

[C#]은는이가 타파!! 자연스러운 문장 만들기!

안녕하십니까 밤말팅입니다! 오늘은 가벼운 걸로 한 번 가져와보았습니다! 게임에 보면 꼭 이런 게 있곤 하죠 밤말팅(은)는 정의의 망치(을)를 얻었다! 한국어에 있는 조사는 "앞에 오는 종성"에

game-part-factory.tistory.com

은는이가 변환은 위 링크에서

확인하실 수 있습니다!

 

이번엔 변환에 그치지 말고!

 

대화창을 만들어봅시다!

 

<목표>

//외부에서 접근하는 모든 함수

//몇 번째 페이지를 읽게 할지 고릅니다.
TextReader.StartRead(int 읽을문장의 번호);

//매 프레임마다 불러 출력을 시도합니다.
TextReader.Update();

//변수를 등록하거나 변경합니다.
TextReader.globalVariable.Set(string 변수명, string 값);

//변수의 값을 가져옵니다.
TextReader.globalVariable.Get(string 변수명);

커맨드내용을 입력하면

천천히 출력해주는 프로그램을

만드는 것이 이번 목표입니다.

 

[Speed:10] 이 프로그램은 대화를

시뮬레이션하도록 제작되었습니다.

 

이렇게 쓰면

아래처럼 나오는 거죠.

이런 느낌으로 대화 출력

제일 먼저 천천히 문자를 출력하는

 

기능을 만든 후에

 

커맨드 인식도 만들어봅시다!

 

스크립트 실행기.zip
0.09MB

시제품은 위의 파일을 다운로드하여

확인하실 수 있습니다.

 

들어있는 [CONTEXT.txt]를 변경하여

원하는 내용을 실행할 수 있습니다.

 

<네임스페이스 참조>

using System;

 

<구상>

 

계산과 출력을 따로 둡니다.

매 업데이트 시기에 받아둔 문자열에서

 

다음 내용을 계산한 후에

 

결괏값을 한 글자씩 출력합니다.

 

더 출력할 값이 없다

 

입력에 다음 계산 내용을 넣는 것으로

 

반복하면 한 글자씩 출력하는

 

프로그램을 만들 수 있을 겁니다.

 

 

 

그리고 이번에는 커맨드 종류를

 

5가지로 늘려보겠습니다!

 

저번에는 변숫값 출력과

조사 처리만 했었는데요,

 

출력, 대입, 조사, 조건, 선택지

로 나누어 봅시다.

 

[ 변수명 ]

 

해당 변수의 값을 가져옵니다.

 

 

[ 변수명 :  ]

 

해당 변수에 값을 대입합니다.

 

 

[ 종성 있음 / 종성 없음 ]

 

직전 글자에 종성이 있으면 왼쪽 값

종성이 없으면 오른쪽 값을 반환합니다.

 

 

[ 조건 ?  : 거짓 ]

 

조건을 확인합니다.

조건은 =, !=, <, >으로 확인합니다.

 

<=이나 >=, <>같은 조합도 됩니다.

 

조건에 변수명만 있으면

 

변수가 등록이 안되어 있거나

 

값이 false라면 거짓 부분 실행

 

등록이 되어 있고 false가 아니라면

 

참 부분을 실행합니다.

 

 

[ ( 대답 ) ? 선택 : 미선택 ]

 

선택지를 만듭니다.

 

현재 출력이 끝난 뒤에 선택하며

 

선택되면 선택 부분을 실행,

 

아니면 미선택 부분을 실행합니다.

 

한 문장에 8개까지 만들 수 있습니다.

 

[ ( 대답 ) ? 선택 ]

 

미선택 부분을 생략할 수도 있습니다.

 

 

 

그리고 예약어도 설정해둡시다.

 

Speed : 출력하는 속도를 정합니다.

(다음 출력까지 시간입니다.)

 

Next : 다음 읽을 줄을 정합니다.

PlayerName : 유저의 이름입니다.

 

TalkName : 상대의 이름입니다.

 

Wait : 출력을 기다립니다.

 

Input : 유저 입력을 받습니다.

 

일단은 이렇게만 만들어도 괜찮겠죠?

 

가봅시다!

 

 

<구현>

핵심 기능을 담을 메인 클래스

 

기반이 될 메인 클래스의 모습입니다.

 

모든 변수와 함수는

 

static으로 이루어질 것이구요

 

그래서 생성자는 필요가 없습니다~

 

내용은 크게

 

출력 부분, 커맨드 부분, 선택지 부분

 

세 가지로 나누면 되겠습니다!

 

그전에 우선 변수부터 보죠~

 

 

//현재 등록된 선택지 배열입니다.
//8개까지 등록할 수 있게 해두었습니다.
private static SelectInformation[] currentSelection = new SelectInformation[8];

//현재 등록된 변수입니다.
//VariableList는 변수 리스트로
//나중에 만들어보도록 합시다.
public static VariableList globalVariable = new VariableList();

//현재 표시되고 있는 이름과 대화 내용을 저장합니다.
private static string talkName = "???";
private static string showText = "";

//출력할 전체 문장입니다.
private static string receiptText = "";

//다음 내용까지 계산해둔 문장입니다.
//여기에서 대화 내용을 꺼내갑니다.
private static string calculatedText = "";

//다음 글자가 출력될 시간입니다.
private static long nextTime = 0;

//전체 문장에서 계산한 위치를 표시합니다.
private static int currentTextIndex = 0;

//계산된 문장에서 출력한 위치를 표시합니다.
private static int currentReadIndex = 0;

//현재 등록된 선택지의 수입니다.
private static int currentSelectionNumber = 0;

//글로벌 스피드는 기본적인 출력 속도를 나타냅니다.
//현재 스피드는 현재 문장의 출력 속도를 나타냅니다.
//문장이 시작할 때 글로벌 스피드에 맞춰지고
//스피드 커맨드가 나오면 바뀝니다.
private static int globalReadSpeed = 10;
private static int currentReadSpeed = 10;

//현재 읽는 페이지와 다음 페이지 위치를 저장합니다.
private static int currentReadPage = 0;
private static int nextReadPage = 0;

//커맨드를 확인할 때, 커맨드 속 커맨드를 위해서
//커맨드 시작 위치와, 겹친 개수를 저장합니다.
//추후 설명하도록 하겠습니다.
private static int commandStartIndex = 0;
private static int commandDepth = 0;

//현재 읽는 중이라고 표시합니다.
private static bool reading = false;

//속도를 무시하는지 확인합니다.
//대화를 진행하는 키 (이 프로젝트에서는 엔터나 스페이스바)
//를 출력 중에 입력하면 즉시
//해당 문장이 로드되게 합니다.
private static bool skip = false;

대강 설명을 해보면

 

선택지배열

 

변수리스트로 저장됩니다.

 

입력받은 문장

계산한 문장

출력한 문장

세 개를 선언하고

 

계산된 위치출력한 위치

따로 저장합니다.

 

출력 속도읽을 페이지

 

읽고 있는지, 스킵할지

 

커맨드를 읽는 중인지

 

확인하는 것으로

 

일단 끝입니다~

 

 

 

출력을 시작해봅시다

변수 선언을 끝냈으니 출력을 시작하도록

 

준비를 해봅시다!

 

public static void StartRead(int page)
{
  //이미 읽고 있었다면, 취소합니다.
  if (reading)
  {
    return;
  };
  
  //읽고 있다고 알립니다.
  reading = true;
  
  //스킵을 꺼둡니다.
  skip = false;

  //현재 받은 페이지 번호를 저장하고
  //다음 페이지를 자동으로 조정합니다.
  currentReadPage = page;
  nextReadPage = page + 1;

  //텍스트를 초기화합니다.
  calculatedText = "";
  showText = "";

  //다음 출력 시간을 0으로 조정해서
  //바로 첫 글자가 출력되게 합니다.
  nextTime = 0;

  //읽기 시작했기 때문에
  //읽은 위치를 0으로 초기화합니다.
  currentTextIndex = 0;
  currentReadIndex = 0;

  //커맨드가 시작된 위치를 0으로 초기화합니다.
  //커맨드 개수도 초기화합니다.
  commandStartIndex = 0;
  commandDepth = 0;

  //현재 읽는 속도를 기본 속도로 맞춥니다.
  currentReadSpeed = globalReadSpeed;

  //저는 TextContainer에서 문자열을 받아옵니다.
  //받아올 위치를 여기에 넣으시면 되겠습니다!
  if (currentReadPage < TextContainer.text.Length)
  {
    receiptText = TextContainer.text[currentReadPage];
  }
  //만약 받아올 문자열이 없는 경우에
  //스크립트 종료 문장을 출력합니다.
  //지금은 출력하는 기능만 있어서 이렇게 만들었는데
  //실제 적용은 다르게 하셔도 됩니다~
  else
  {
    talkName = "<System>";
    receiptText = "스크립트가 종료되었습니다.";
    nextReadPage = page;
  };

  //상대 이름을 초기화합니다.
  string showingName = talkName;
  
  //지금은 콘솔로 출력하기 때문에
  //화면을 모두 지우고 상대 이름을 써주겠습니다.
  Console.Clear();
  Console.WriteLine(showingName);
}

출력 준비는 초기화로 시작합시다~

 

이미 읽는 중이면 취소를 하고

 

아니면 변수를 초기화시켜줍니다.

 

그리고 여기선 페이지 번호를 받아서

 

TextContainer에 있는 값을 받는데요,

 

다른 곳에서 정보를 받아오시는 분은

 

이 부분을 수정하시면 되겠습니다!

 

 

 

 

매 프레임 실행할 부분

public static void Update()
{
  //지금 출력 중인 경우
  if (reading)
  {
    //엔터 키나 스페이스 바를 누른 경우
    if(currentKey == ConsoleKey.Enter || currentKey == ConsoleKey.Spacebar)
    {
      //눌린 키를 초기화시킵니다.
      currentKey = 0;
      
      //스킵을 활성화합니다.
      skip = true;
    };

    //출력 함수를 실행합니다.
    OnRead();
  }
  //출력 중이 아닌 경우
  else
  {
    //엔터나 스페이스바를 누른 경우
    if(currentKey == ConsoleKey.Enter || currentKey == ConsoleKey.Spacebar)
    {
      //등록된 선택지가 없을 때
      if(currentSelectionNumber <= 0)
      {
        //키를 초기화합니다.
        currentKey = 0;

        //다음 페이지를 실행합니다.
        StartRead(nextReadPage);
      };
    }
    //선택지가 있을 때 다른 키를 누르면
    //해당 위치의 선택지를 고릅니다.
    else if(currentKey == ConsoleKey.A)
    {
      ChoiceSelection(0);
    }
    else if (currentKey == ConsoleKey.S)
    {
      ChoiceSelection(1);
    }
    else if (currentKey == ConsoleKey.D)
    {
      ChoiceSelection(2);
    }
    else if (currentKey == ConsoleKey.F)
    {
      ChoiceSelection(3);
    }
    else if (currentKey == ConsoleKey.Z)
    {
      ChoiceSelection(4);
    }
    else if (currentKey == ConsoleKey.X)
    {
      ChoiceSelection(5);
    }
    else if (currentKey == ConsoleKey.C)
    {
      ChoiceSelection(6);
    }
    else if (currentKey == ConsoleKey.V)
    {
      ChoiceSelection(7);
    };
  };
}

매 프레임마다 실행할

 

업데이트 함수입니다.

 

 

[출력 중]이면 

 

스킵 버튼 확인만 해보고

 

바로 출력 함수를 실행해주고

 

 

[출력 중이 아닐 때]는

 

스킵 버튼을 누르면

 

선택지 없을 때 다음 문장 출력

 

다른 버튼을 누르면

 

해당 위치의 선택지

 

선택하도록 합시다~

 

 

만약 선택지 선택

 

다른 방법으로 구현하시면

 

이 부분은 그냥 스킵만

 

구현해두시면 되겠습니다!

 

 

 

실제 출력하는 함수

 

public static void OnRead()
{
  if (skip || nextTime <= time.ElapsedMilliseconds)
  {
    if(계산된 글자를 모두 출력 못 했을 경우)
    else if(입력된 글자를 모두 계산 못 했을 경우);
    
    if(모두 계산이 끝났고, 모두 출력이 끝난 경우);
  };
}

 

이런 구조로 만들 겁니다!

 

우선 스킵이 켜져 있거나

 

다음 출력 시간을 넘었을 때

 

실행할 거구요

 

모두 출력이 됐는지 확인

 

모두 계산이 됐는지 확인

 

모두 끝났는지 확인

 

세 가지 순서로 갑니다~

 

 

if (currentReadIndex < calculatedText.Length)
{
  //현재 위치의 텍스트를 출력 텍스트에 추가합니다.
  showText += calculatedText[currentReadIndex];
  
  //콘솔이기 때문에, 직접 출력해주도록 하겠습니다.
  Console.Write(calculatedText[currentReadIndex]);
  
  //읽는 위치를 한 칸 뒤로 옮겨놓습니다.
  ++currentReadIndex;

  //다음 출력 시간을 현재시간 + 읽기 속도로 저장해둡니다.
  nextTime = time.ElapsedMilliseconds + currentReadSpeed;
}

출력한 위치가 계산된 길이보다 작을 때

 

아직 출력할 것이 남았기 때문에

 

이 부분을 실행합니다~

 

한 글자만 출력한 뒤에

 

다음 출력 시간을 조정해둡니다.

 

 

 

else if (currentTextIndex < receiptText.Length)
{
  //'['가 나오면 커맨드가 시작한 것으로 봅니다.
  if (receiptText[currentTextIndex] == '[')
  {
    //커맨드가 시작한 위치를 저장해 둡니다.
    commandStartIndex = currentTextIndex;
    
    //다음 문자를 읽기 위해 위치를 조정합니다.
    ++currentTextIndex;
    
    //현재 커맨드 깊이는 1입니다.
    //']'가 1개 더 나와야 커맨드가 끝난다는 뜻입니다.
    commandDepth = 1;

    //입력받은 텍스트가 끝날 때까지 반복합니다.
    while (currentTextIndex < receiptText.Length)
    {
      //'['가 나오면 깊이를 1 더합니다.
      if (receiptText[currentTextIndex] == '[')
      {
        ++commandDepth;
      }
      //']'가 나오면 깊이를 1 뺍니다.
      else if (receiptText[currentTextIndex] == ']')
      {
        --commandDepth;
      };

      //깊이가 0이 된 경우에 커맨드가 끝났다고 봅니다.
      if (commandDepth <= 0)
      {
        //앞에 계산된 텍스트가 있는 경우
        if (calculatedText.Length - 1 > 0)
        {
          //마지막 글자와, 커맨드 전체를 확인해서 계산된 글자에 넣습니다.
          //커맨드 확인은 나중에 설명드리도록 하겠습니다.
          calculatedText += CommandChecker(calculatedText[calculatedText.Length - 1], receiptText.Substring(commandStartIndex, currentTextIndex - commandStartIndex + 1));
        }
        //앞에 계산된 텍스트가 없는 경우
        //똑같이 커맨드를 넣는데, 마지막 글자가 공백인 것으로 합니다.
        else
        {
          calculatedText += CommandChecker(' ', receiptText.Substring(commandStartIndex, currentTextIndex - commandStartIndex + 1));
        };
        
        //다음 글자를 읽게 하기 위해 위치를 옮겨둡니다.
        ++currentTextIndex;

        break;
      }
      //커맨드 깊이가 아직 1이상인 경우
      //커맨드가 끝나지 않았기 때문에 계속 갑니다.
      else
      {
        ++currentTextIndex;
      };
    };
  }
  //커맨드가 아닌 경우에는
  //현재 글자를 계산된 문장에 추가합니다.
  else
  {
    calculatedText += receiptText[currentTextIndex];
    ++currentTextIndex;
  };
};

계산된 문장이 끝나고

 

아직 계산할 문장이 남은 경우

 

실행하는 부분입니다.

 

 

커맨드가 아닌 일반 글자인 경우

 

그냥 한 글자 추가를 하지만

 

 

커맨드에 돌입했을 땐 달라집니다.

 

 

[ PlayerName : 밤말팅 ]

 

같은 모습이면 '['면 시작, ']'면 끝으로

 

처리하기만 해도 돼서 참 편하겠지만

 

이걸로는 그냥 정해진 걸로만

 

실행이 되겠지요?

 

 

[ PlayerName : [Input] ]

 

이런 것도 처리해야 할 겁니다.

 

커맨드 읽기는 나중에 할 거라서

 

이걸 처리해서 보내주는 것만 해보죠.

 

 

'['이 나오면 깊이에 1을 더합니다.

 

1                    2           

[ PlayerName : [Input] ]

 

 

 

']'이 나오면 깊이에 1을 뺍니다.

 

1                    2      1 0

[ PlayerName : [Input] ]

 

0이 된 순간처음 '['위치부터

 

현재 위치까지 문자열을 넘겨주면

 

커맨드 확인 함수가 알아서 해줄 테니

 

일단은 여기까지 해둡시다.

 

 

if (currentTextIndex >= receiptText.Length && currentReadIndex >= calculatedText.Length)
{
  //읽기가 끝났다고 알립니다.
  reading = false;
  
  //다음 문장이 스킵되지 않게 꺼둡니다.
  skip = false;

  Console.Write('\n');

  //선택지 개수에 맞게 선택지 표시를 합니다.
  if (currentSelectionNumber > 0)
  {
    Console.WriteLine("A : " + currentSelection[0].context);
  };
  if (currentSelectionNumber > 1)
  {
    Console.WriteLine("S : " + currentSelection[1].context);
  };
  if (currentSelectionNumber > 2)
  {
    Console.WriteLine("D : " + currentSelection[2].context);
  };
  if (currentSelectionNumber > 3)
  {
    Console.WriteLine("F : " + currentSelection[3].context);
  };
  if (currentSelectionNumber > 4)
  {
    Console.WriteLine("Z : " + currentSelection[4].context);
  };
  if (currentSelectionNumber > 5)
  {
    Console.WriteLine("X : " + currentSelection[5].context);
  };
  if (currentSelectionNumber > 6)
  {
    Console.WriteLine("C : " + currentSelection[6].context);
  };
  if (currentSelectionNumber > 7)
  {
    Console.WriteLine("V : " + currentSelection[7].context);
  };
  
  return;
};

계산할 문장도 없고

 

출력할 문장도 없어진 상태에서

 

실행할 부분입니다.

 

 

읽기 끝났다고 알려준 후

 

스킵을 끄고

 

선택지 표시하는 것이 끝입니다.

 

 

별거 없죠?

 

이걸로 출력 부분은 끝입니다.

 

 

인식한 커맨드를 실행합니다.

 

private static string CommandText(string targetText)
{
  //결과값을 저장할 문자열입니다.
  //우선은 받아 온 그대로 넣어둡시다.
  string returnText = targetText;
  
  //커맨드 깊이를 저장합시다.
  int commandStartIndex = 0;
  int commandDepth = 0;

  //문자열에서 추출한 커맨드를 저장할 공간입니다.
  string checkedCommand;

  //문자열 전체를 돌면서 확인해봅시다.
  for (int index = 0; index < returnText.Length; ++index)
  {
    //'['가 나타나면
    if (returnText[index] == '[')
    {
      //커맨드 시작 위치가 지정이 안되어있으면 지정합니다.
      if(commandStartIndex == 0)
      {
        commandStartIndex = index;
      };
      
      //커맨드 깊이를 1 늘립니다.
      ++commandDepth;
    }
    //']'가 나타나면
    else if (returnText[index] == ']')
    {
      //커맨드 깊이를 1 줄여봅니다.
      --commandDepth;
      
      //커맨드가 끝났다고 판단되었을 때
      if (commandDepth <= 0)
      {
        //커맨드를 저장해둡니다.
        checkedCommand = returnText.Substring(commandStartIndex, index - commandStartIndex + 1);

        //반환할 텍스트에서 해당 커맨드 부분을 제거합니다.
        returnText = returnText.Remove(commandStartIndex, index - commandStartIndex + 1);
        
        //커맨드 시작위치로 이동합니다.
        //이렇게 해야 놓치는 글자가 없이 확인 가능합니다.
        index = commandStartIndex;

        //저장된 커맨드가 null값이 아닐 때에만 실행합니다.
        if (checkedCommand != null)
        {
          //시작 위치가 0 이하인 경우
          if (index <= 0)
          {
            //위치를 0으로 보정한 후
            index = 0;

            //반환할 텍스트 앞부분에 커맨드의 반환값을 넣어둡니다.
            returnText = CommandChecker(' ', checkedCommand) + returnText;
          }
          //시작 위치가 0보다 큰 경우
          else
          {
            //반환할 문자열의 길이가 0보다 큰 경우
            if (returnText != null && returnText.Length > 0)
            {
              //문자열에 커맨드 반환값을 넣습니다.
              returnText = returnText.Insert(index, CommandChecker(returnText[index - 1], checkedCommand));
            }
            //반환할 문자열이 없는 경우
            else
            {
              //커맨드 반환값을 반환할 문자열로 지정합니다.
              returnText = CommandChecker(' ', checkedCommand);
            };
          };
        };

        //검색 위치를 한 칸 앞으로 이동해서
        //커맨드로 인해 생겨난 글자도 확인합니다.
        //위치를 최소 0으로 맞춰놓았고
        //반복문에서 +1이 되기 때문에
        //음수로 내려가도 음수 배열을 참조하지 않습니다.
        --index;
      };
    };

    //모든 작업이 끝난 뒤에 남은 텍스트가 없으면
    //반복문 중간에도 null을 반환하고 끝냅니다.
    if (returnText == null)
    {
      return null;
    };
  };

  //정상적으로 모두 끝났다면 완료된 문자열을 반환합니다.
  return returnText;
}

 

문자열을 받아서 커맨드 부분을 실행한 뒤

 

나온 결과 문자열을 반환하는 함수입니다.

 

커맨드 부분만 추출해서 실행하고

 

다시 넣어주고 있습니다.

 

 

문자열에 저장한다는 특성상

 

여러 가지 커맨드를 실행시킨 후

 

결과를 모두 하나로 합쳐주는 역할

 

하고 있습니다!

 

 

그래서 CommandText인 것이죠~

 

 

합쳐 줄 함수를 만들었으니

 

개별로 확인할 함수도 만들어야겠죠?

 

위에 나온 CommandChecker

 

확인해보도록 합시다!

 

private static string CommandChecker(char preText, string targetCommand)
{
  //받은 글자가 2개 이하라면 []만 있거나
  //제대로 받은 것이 아니기 때문에 공백을 반환합니다.
  if (targetCommand.Length <= 2)
  {
    return "";
  }
  //2개 이상이면 앞 뒤 []를 자릅니다.
  else
  {
    targetCommand = targetCommand.Substring(1, targetCommand.Length - 2);
  };

  //커맨드가 나뉘어지는 부분을 저장합니다.
  int divideIndex = 0;
  
  //현재 커맨드 외에 다른 커맨드가 있으면
  //인식 대상에서 제외하기 위해서
  //커맨드 깊이를 계산합니다.
  int commandDepth = 0;
  
  //인식한 커맨드가 어떤 커맨드인지 분류합니다.
  CommandType currentCommand = CommandType.GetVariable;
  
  //반환할 텍스트를 저장합니다.
  string returnText = "";
  
  //커맨드 분류와 실행을
  //이 부분에 넣습니다.
  
  //결과물을 한 번 더 공정해서 내보냅니다.
  if (returnText == null || returnText.Length <= 0)
  {
    return null;
  }
  else
  {
    return CommandText(returnText);
  };
}

 

CommandChecker의 앞부분입니다.

 

[] 안에 한 글자라도 있어야

 

커맨드로 인식할 수 있겠죠?

 

[]를 포함하기 때문에

 

2글자를 초과할 때만 넘겨줍니다.

 

제일 바깥쪽의 []는 방해가 되기 때문에

 

넘겨줄 때 자르면서 시작합시다.

 

[ 왼쪽 ? 오른쪽 ]

[ 왼쪽 : 오른쪽 ]

?: 같은 커맨드 기점이 나오면

 

위치를 저장해두도록 합시다.

 

 

그리고 분류와 실행이 끝나면

 

혹시 모르니 한 번 더 가공해주는 것으로

 

함수를 끝내면 되겠습니다!

 

 

분류된 커맨드는 다음과 같습니다.

 

private enum CommandType
{
  //변수 값 반환
  GetVariable,
  
  //변수 값 대입
  Substitute,
  
  //조사 표현
  Postposition,
  
  //조건문 확인
  Condition,
  
  //선택지 생성
  Select
}

 

반환, 대입, 조사, 조건, 선택

 

다섯 개로 해두고

 

분류 작업을 시작합시다.

 

 

for (int index = 0; index < targetCommand.Length; ++index)
{
  //'['나 '('가 나오면 깊이 추가
  //']'나 ')'가 나오면 한 칸 빠져나옵니다.
  if (targetCommand[index] == '[' || targetCommand[index] == '(')
  {
    ++commandDepth;
  }
  else if (targetCommand[index] == ']' || targetCommand[index] == ')')
  {
    --commandDepth;
  };

  //깊이가 0일 때만 열기.
  //순수 본인 커맨드만 읽도록 합시다.
  if (commandDepth == 0)
  {
    // '/'가 있으면 [은/는]과 같이 조사 표현입니다.
    if (targetCommand[index] == '/')
    {
      currentCommand = CommandType.Postposition;
      divideIndex = index;
      break;
    }
    // ':'가 있으면 대입입니다.
    else if (targetCommand[index] == ':')
    {
      currentCommand = CommandType.Substitute;
      divideIndex = index;
      break;
    }
    // '?'가 있으면 두 가지 상황으로 나뉩니다.
    else if (targetCommand[index] == '?')
    {
      //일단 나뉜 부분은 공통이기 때문에
      //여기서 나뉘다고 알립니다.
      divideIndex = index;
      
      //왼쪽 부분만 떼어옵니다.
      string leftArgument = targetCommand.Substring(0, divideIndex).Trim();
      
      //왼쪽 부분의 맨 처음이 '('일 때, 맨 마지막이 ')'인 것이 확인되면
      //현재 커맨드는 선택지임을 알립니다.
      if (leftArgument.Length > 0 && leftArgument[0] == '(' && leftArgument[leftArgument.Length - 1] == ')')
      {
        currentCommand = CommandType.Select;
        break;
      }
      // '( 내용 )' 형태가 아닌 경우
      //현재 커맨드가 조건문임을 알립니다.
      else
      {
        currentCommand = CommandType.Condition;
        break;
      };
    };
  };
};

 

[ [조건?참:거짓] : TRUE ]

 

이런 커맨드를 처리를 할 때

 

안에 있는 커맨드는 무시를 하고

 

현재 커맨드가 무엇인지

 

알아야 하기 때문에

 

[ ] 안은 무시하기로 합시다.

 

그럼 커맨드 깊이가 0일 때만 읽어야겠죠!

 

 

깊이가 0이고 처음 나온 기호를 사용합시다.

 

[ 종성 있을 때 / 종성 없을 때]

[변수명 : 값]

 

형태는 그냥 바로 확인하면 되지만

 

[ 조건 ? 참 : 거짓]

[ (선택지) ? 선택시 : 미선택 ]

 

같은 경우에는 처음이 ?로 같습니다.

 

그래서 왼쪽 부분을 확인해서

 

(내용)의 형태로 되어있는지만

 

확인해주면 되겠습니다~

 

 

if (currentCommand == CommandType.Postposition)
{
  //함수 시작에 받아온 직전 문자를 사용합니다.
  //직전 문자가 종성이 있으면 왼쪽, 없으면 오른쪽을 반환합니다.
  //반환할 때, 혹시 모르니 커맨드를 읽어본 후에 반환합시다.
  if (FinalConsonant(preText))
  {
    returnText = CommandText(targetCommand.Substring(0, divideIndex).Trim());
  }
  else
  {
    returnText = CommandText(targetCommand.Substring(divideIndex + 1, targetCommand.Length - divideIndex - 1).Trim());
  };
}

첫 번째로 처리할 내용은

 

[ 은 / 는 ]의 형태입니다.

 

앞 글자를 미리 CommandChecker에서

 

인자로 받아오기 때문에

 

그 글자가 종성이 있는가만 확인해서

 

반환할 위치의 문자열을 내보냅시다.

 

 

[C#]은는이가 타파!! 자연스러운 문장 만들기!

안녕하십니까 밤말팅입니다! 오늘은 가벼운 걸로 한 번 가져와보았습니다! 게임에 보면 꼭 이런 게 있곤 하죠 밤말팅(은)는 정의의 망치(을)를 얻었다! 한국어에 있는 조사는 "앞에 오는 종성"에

game-part-factory.tistory.com

종성의 확인은 위 링크에서

 

확인하실 수 있습니다~

 

 

else if (currentCommand == CommandType.Substitute)
{
  //0번은 왼쪽입니다.
  string arg0 = CommandText(targetCommand.Substring(0, divideIndex).Trim());
  
  //1번은 오른쪽입니다.
  string arg1 = CommandText(targetCommand.Substring(divideIndex + 1, targetCommand.Length - divideIndex - 1).Trim());

  //왼쪽 문자열을 구분해서 사용합니다.
  //미리 예약된 변수는 분류해서 넣어줍시다.
  
  // Speed에 대입을 시도한 경우
  if (arg0 == "Speed")
  {
    //문자열을 숫자로 바꾸기 위해 int를 선언해둡니다.
    int checkedNumber;

    //오른쪽 값을 숫자로 바꿉니다.
    if (arg1.Length > 0 && int.TryParse(arg1, out checkedNumber))
    {
      //바꾼 값을 현재 읽기 속도에 대입합니다.
      currentReadSpeed = checkedNumber;
    }
    //오른쪽 값이 비어있거나 숫자가 아닌 경우
    else
    {
      //기본 읽는 속도를 대입해놓습니다.
      currentReadSpeed = globalReadSpeed;
    };
  }
  // TalkName에 대입할 경우
  else if (arg0 == "TalkName")
  {
    //오른쪽 문자열을 그대로 대상 이름에 넣습니다.
    ChangeName(arg1);
  }
  // Wait에 대입할 경우
  else if(arg0 == "Wait")
  {
    //문자열을 숫자로 저장하기 위해 int를 선언해둡니다.
    int checkedNumber;

    //오른쪽에 숫자가 있는 경우
    if (arg1.Length > 0 && int.TryParse(arg1, out checkedNumber))
    {
      //다음 출력 시간을
      //현재 시간 + 대기 시간으로 조정합니다.
      nextTime = time.ElapsedMilliseconds + checkedNumber;
    };
  }
  // Next에 대입할 경우
  else if(arg0 == "Next")
  {
    //숫자로 만듭시다.
    int checkedNumber;

    if(arg1.Length > 0 && int.TryParse(arg1, out checkedNumber))
    {
      //맨 앞 글자가 +나 -라면
      //현재 페이지에 +나 -한 값을 다음 페이지로 둡니다.
      if(arg1[0] == '+' || arg1[0] == '-')
      {
        nextReadPage = currentReadPage + checkedNumber;
      }
      //그냥 숫자만 있으면 바로 대입합니다.
      //숫자에 -1이 된 이유는 메모장 줄 읽는 것과
      //배열의 위치가 달라서 그런 것이니
      //이 부분은 조정하시면 됩니다!
      else
      {
        nextReadPage = checkedNumber - 1;
      };

      //혹시나 페이지가 0 이하로 떨어지면 0으로 보정합니다.
      if(nextReadPage < 0)
      {
        nextReadPage = 0;
      };
    };
  }
  //예약어에 속하지 않는 경우에는
  //변수 목록에 추가를 시도합니다.
  else
  {
    globalVariable.Set(arg0, arg1);
  };
}

두 번째로 처리할 내용은

 

[ 변수 : 값 ] 형태입니다.

 

 

왼쪽 변수오른쪽 값을 넣으면

 

끝나는 것이기 때문에

 

 

짧게 끝날 수도 있겠지만

 

예약어를 미리 둬서

 

일반 변수 쓰는 걸 좀 줄여 봅시다!

 

 

글자로 변수를 찾는 것

 

생각보다 시간이 걸리니까요!

 

 

예약어 처리는 

 

각 예약어마다 다르기 때문에

 

위에서 확인을 해보시고

 

 

딱 한 줄만 되어있는 부분을

 

파헤쳐 봅시다.

 

변수가 들어갈 리스트

 

그러면 잠시 클래스를 옮겨가야 합니다.

 

변수를 저장할 리스트이구요!

 

[단일 연결 리스트]로 만들어봅시다!

 

 

public class VariableList
{
  //첫 번째 원소를 가리킵니다.
  public Variable start;
  
  //마지막 원소를 가리킵니다.
  public Variable end;

  //마지막으로 사용한 원소를 가리킵니다.
  public Variable lastContactVariable;

  //원소의 개수를 받아올 수 있게 읽기 전용으로 둡니다.
  public int count { get { return realCount; } }
  
  //실제 원소 개수는 여기서 기록합니다.
  private int realCount;

  //생성자는 그냥 모두 초기화시킵니다.
  public VariableList()
  {
    start = null;
    end = null;
    lastContactVariable = null;
    lastContactIndex = 0;
  }
}

 

처음 원소와

 

마지막으로 접근한 원소를 저장하고

 

개수만 표시하는

 

아주 간단한 구조입니다.

 

 

각 원소에는

 

변수명, 값, 다음 변수 주소

 

딱 세 개만 쓰여있죠.

 

 

일단 변수를 찾아오는 것부터 시작합시다.

 

변수를 확인해봅시다.

 

public Variable Find(string wantName)
{
  //리스트에 남은 것이 없으면 바로 null을 반환합니다.
  if (count <= 0)
  {
    return null;
  };

  //마지막으로 사용한 원소를 먼저 확인합니다.
  if (lastContactVariable.name == wantName)
  {
    return lastContactVariable;
  };

  //변수 검색을 시작하기 위해서
  //처음 변수를 등록합니다.
  Variable check = start;

  //모든 원소를 돌면서 확인해봅시다.
  for (int index = 0; index < count; ++index)
  {
    //대상이 없는지는 확인한 후에
    if (check != null)
    {
      //현재 확인 중인 원소의 이름을 확인합니다.
      if (check.name == wantName)
      {
        //마지막으로 접근한 위치를 저장합니다.
        lastContactVariable = check;
        
        //현재 확인한 원소를 반환합니다.
        return check;
      }
      //아니면 다음 원소로 갑니다.
      else
      {
        check = check.next;
      };
    }
    //대상이 없으면 바로 null을 반환합니다.
    else
    {
      return null;
    };
  };

  //다 돌아도 없으면 null을 반환합니다.
  return null;
}

 

우선 마지막으로 접근한 원소를 보고

 

없으면 돌면서 

 

이름을 대조해보고

 

맞으면 반환하는 것이 끝입니다!

 

 

public string Get(string wantName)
{
  Variable target = Find(wantName);
  
  if (target != null)
  {
    return target.value;
  };

  return null;
}

 

찾기가 끝났으면 쉬운 것이

 

값을 받아오는 것이죠!

 

변수가 있으면 해당 위치의 값

 

그대로 가져옵니다.

 

 

변수를 넣어봅시다.

public void Set(string wantName, string wantValue)
{
  //대상을 찾아옵니다.
  Variable target = Find(wantName);

  //대상이 없으면
  if(target == null)
  {
    //새로운 변수를 만듭니다.
    target = new Variable(wantName, wantValue);

    //마지막 원소가 있다면
    if (end != null)
    {
      //마지막 원소의 다음 원소로 
      //새로 만든 원소를 넣습니다.
      end.next = target;
    }
    //마지막 원소가 없다면
    //첫 번째 원소도 없는 것이기 때문에
    //첫 번째 원소를 새로운 원소로 등록합니다.
    else
    {
      start = target;
    };
    
    //마지막 원소로 등록합니다.
    end = target;
    
    //마지막으로 접근한 원소를 조정합니다.
    lastContactVariable = target;

    //원소 개수 표시를 늘립니다.
    ++realCount;
  }
  //찾은 대상이 있으면 대상의 값을 바꿉니다.
  else
  {
    target.value = wantValue;
  };
}

변수 입력은 변수 확인을 하고

 

이미 있는 변수

 

값만 살짝 바꿔주고

 

없었던 변수라면

 

새로 만들어 등록해줍니다.

 

 

 

다시 커맨드 확인으로

 

그럼 변수 입력을 끝냈으니

 

다시 커맨드로 돌아갑시다~

 

else if (currentCommand == CommandType.GetVariable)
{
  //각 예약어마다 변수를 돌려줍니다.
  if (targetCommand == "Speed")
  {
    returnText = Convert.ToString(currentReadSpeed);
  }
  else if (targetCommand == "TalkName")
  {
    returnText = talkName;
  }
  else if(targetCommand == "Next")
  {
    returnText = Convert.ToString(nextReadPage);
  }
  else if(targetCommand == "Wait")
  {
    returnText = null;
  }
  // Input명령이 나오면 입력 창으로 이동합니다.
  else if(targetCommand == "Input")
  {
    //입력을 보여줍니다.
    Console.Write('\n');
    Console.WriteLine("입력 :");
    returnText = Console.ReadLine();
    
    //입력이 끝나면 원래 보여주던 내용을 다시 보여줍니다.
    Console.Clear();
    Console.WriteLine(talkName);
    Console.Write(showText);
  }
  //예약어가 없으면 등록된 변수가 있는지 확인합니다.
  else
  {
    returnText = globalVariable.Get(CommandText(targetCommand));
  };
}

 

예약어가 나오면 그냥 돌려주지만

 

Input이 나오면

 

잠깐 입력 창을 띄웁니다.

 

다른 변수를 찾을 땐

 

변수 리스트에서 가져옵시다!

 

 

 

다음은 조건식을 만들어 볼 건데요!

 

제일 길기 때문에 나눠서

 

설명해드리도록 할게요.

 

 

크게는 기호 확인, 조건 확인, 실행

 

세 가지로 갑니다.

 

일단 기본 세팅부터 갑시다.

 

else if(currentCommand == CommandType.Condition)
{
  string arg0 = targetCommand.Substring(0, divideIndex).Trim();
  string arg1 = targetCommand.Substring(divideIndex + 1, targetCommand.Length - divideIndex - 1).Trim();

  bool conditionCheck = false;
  
  //여기에 아래 내용들이 들어갑니다.
}

일단 0번은 왼쪽

 

1번은 오른쪽 저장해 두고

 

conditionCheck

 

결과가 참인지 확인합니다.

 

제일 먼저 왼쪽에서 하는

 

기호 확인조건 확인부터 합시다.

 

if(arg0.Length > 0)
{
  //기호의 왼쪽 끝입니다.
  int leftEnd = 0;
  
  //기호의 오른쪽 끝입니다.
  int rightStart = 0;

  // '='가 있는지 확인합니다.
  bool checkEqual = false;
  
  // '!='인지 확인합니다.
  bool checkNotEqual = false;
  
  // '>'인지 확인합니다.
  bool checkBigger = false;
  
  // '<'인지 확인합니다.
  bool checkSmaller = false;
  
  //여기에 기호 확인과
  
  //조건 확인이 들어갑니다.
}
//조건이 없었을 땐, 무조건 참으로 계산합니다.
else
{
  conditionCheck = true;
};

조건식 부분을 확인하는

 

기본 부분입니다.

 

 

기호는 두 개 이상이 될 수 있어서

 

시작끝 부분을 저장해둡시다.

 

 

=, !=, <, > 여부를 확인할

 

bool값을 선언해두겠습니다.

 

 

for (int index = 0; index < arg0.Length; ++index)
{
  // '='가 나왔을 때
  if (arg0[index] == '=')
  {
    //바로 앞에 '!'가 있으면
    if(index > 0 && arg0[index - 1] == '!')
    {
      //같지 않다고 확인합니다.
      checkNotEqual = true;
      
      //왼쪽 끝이 맞춰져 있지 않으면
      //'!' 전이 왼쪽 끝이라고 알립니다.
      if(leftEnd == 0)
      {
        leftEnd = index - 2;
        
        if(leftEnd < 0)
        {
          leftEnd = 0;
        };
      };
      //오른쪽 끝을 조정합니다.
      rightStart = index + 1;
    }
    //바로 앞에 '!'가 없고 같지 않아야 한다고 등록되지 않았을 때
    else if(!checkNotEqual)
    {
      //등호를 켭니다.
      checkEqual = true;
      
      //왼쪽 끝과 오른쪽 끝을 조정합니다.
      if (leftEnd == 0)
      {
        leftEnd = index - 1;
      };
      rightStart = index + 1;
    };
  }
  //부등호도 같은 방식으로 등록합니다.
  else if(arg0[index] == '>')
  {
    checkBigger = true;
    if (leftEnd == 0)
    {
      leftEnd = index - 1;
    };
    rightStart = index + 1;
  }
  else if (arg0[index] == '<')
  {
    checkSmaller = true;
    if (leftEnd == 0)
    {
      leftEnd = index - 1;
    };
    rightStart = index + 1;
  };
};

기호 확인은 위치를 맞추는 것도

 

같이 하도록 합니다.

 

조건식에서 왼쪽과 오른쪽을

 

구분하기 위해 기호가 나왔을 때

 

위치를 조금 조정합니다.

 

 

그리고 각 기호에 맞는

 

스위치를 켜주는 것으로

 

기호 확인은 끝입니다.

 

 

//양쪽 다 위치가 0인 경우
//기호가 없는 것으로 판단합니다.
if(leftEnd == 0 && rightStart == 0)
{
  //조건식에 이름만 있는 것으로 보고
  //해당 이름을 가진 변수를 받아옵니다.
  //해당 변수가 있고, 값이 false가 아니면 true로 칩니다.
  if (globalVariable.Get(CommandText(arg0)) != null && globalVariable.Get(CommandText(arg0)).ToLower() != "false")
  {
    conditionCheck = true;
  };
}
//기호를 발견한 경우
else
{
  //변수의 값을 받아옵니다.
  //변수 이름을 조정하기 위해서 커맨드를 모두 실행해둡니다.
  string leftText = CommandText(arg0.Substring(0, leftEnd).Trim());
  string leftValue = globalVariable.Get(leftText);
  if (leftValue == null)
  {
    leftValue = leftText;
  };

  //오른쪽은 확인할 값입니다.
  //역시 커맨드를 실행해둔 후 값을 받아옵니다.
  string rightText = CommandText(arg0.Substring(rightStart, arg0.Length - rightStart).Trim());
  string rightValue = globalVariable.Get(rightText);
  if (rightValue == null)
  {
    rightValue = rightText;
  };

  //확인할 스위치에 맞는 연산을 진행합니다.
  if (checkEqual && leftValue == rightValue)
  {
    conditionCheck = true;
  }
  else if (checkNotEqual && leftValue != rightValue)
  {
    conditionCheck = true;
  };

  //같거나 같지 않거나 상관 없이
  //부등호는 그대로 진행합니다.
  if (checkBigger || checkSmaller)
  {
    //왼쪽과 오른쪽은 모두 숫자여야합니다.
    int leftNumber;
    int rightNumber;

    if (int.TryParse(leftValue, out leftNumber) && int.TryParse(rightValue, out rightNumber))
    {
      //해당 토큰에 맞는 연산을 진행합니다.
      if (checkBigger && leftNumber > rightNumber)
      {
        conditionCheck = true;
      };

      if (checkSmaller && leftNumber < rightNumber)
      {
        conditionCheck = true;
      };
    };
  };
};

 

조건 확인은 간단합니다.

 

이미 스위치를 받아왔기 때문에

 

양쪽 값을 직접 연산해줘야 하는데요

 

'='와 '!='는 양립할 수 없기 때문에

 

둘 중 하나만 해주고

 

 

부등호는 다른 것과 상관없이

 

언제나 확인합니다.

 

 

조건을 확인하는 중에

 

어떤 것이라도 통과하면

 

이 되는 시스템입니다!

 

 

그렇게 하면 >=<=

 

둘 중 하나만 적용되면

 

통과할 수 있겠죠?

 

 

극단적으로 <>로 하면

 

!=와 같은 역할이 되고

 

 

<=>는 항상 참이 되겠죠?

 

이런 느낌으로 만들어보았습니다~

 

 

//나뉘는 위치를 표시합니다.
int resultDivideIndex = -4;

//커맨드 깊이를 저장합니다.
int rightCommandDepth = 0;

//다른 커맨드가 닿지 않는 ':'를 찾아서
//나뉘는 위치로 설정합니다.
for(int index = 0; index < arg1.Length; ++index)
{
  if(arg1[index] == '[')
  {
    ++rightCommandDepth;
  }
  else if(arg1[index] == ']')
  {
    --rightCommandDepth;
  }
  else if(arg1[index] == ':' && rightCommandDepth == 0)
  {
    resultDivideIndex = index;
    break;
  };
};

//값이 참이거나, 나뉘지 않았을 경우
if (conditionCheck || resultDivideIndex < 0)
{
  //나뉘지 않아서 왔을 때는 문자열을 그대로 반환합니다.
  if(resultDivideIndex < 0)
  {
    return CommandText(arg1.Trim());
  }
  //참이어서 왔을 때에는 참 부분만 반환합니다.
  else
  {
    return CommandText(arg1.Substring(0, resultDivideIndex).Trim());
  };
}
//거짓일 경우
else
{
  //거짓 부분을 반환합니다.
  return CommandText(arg1.Substring(resultDivideIndex + 1, arg1.Length - resultDivideIndex - 1).Trim());
};

 

실행 부분은 이미

 

커맨드 실행을 만들었기 때문에

 

왼쪽 오른쪽만 나누고

 

가공되지 않은 문자

 

그대로 실행에 넘겨줍시다.

 

 

주의할 점은

 

[참 : 거짓]의 형태가

 

아닌 경우엔

 

그냥 전체를 실행합니다.

 

 

안 그러면 아무것도

 

실행하지 않게 됩니다~

 

 

 

else if(currentCommand == CommandType.Select)
{
  //왼쪽에 있는 것은 "(내용)"의 형태이기 때문에
  //앞뒤를 잘라서 "내용"으로 만듭니다.
  string arg0 = targetCommand.Substring(0, divideIndex - 1).Trim();
  arg0 = arg0.Substring(1, arg0.Length - 2).Trim();
  
  //오른쪽은 그냥 둡니다.
  string arg1 = targetCommand.Substring(divideIndex + 1, targetCommand.Length - divideIndex - 1).Trim();

  //오른쪽 구분용으로 놔둡시다.
  int rightDivide = 0;
  int rightCommandDepth = 0;
  bool rightDivided = false;
  
  //오른쪽을 ':'기준으로 나눕니다.
  if(arg1.Length > 0)
  {
    for(int index = 0; index < arg1.Length; ++index)
    {
        if(arg1[index] == '[')
      {
        ++rightCommandDepth;
      }
      else
      {
        --rightCommandDepth;
      };

      if(rightCommandDepth == 0 && arg1[index] == ':')
      {
        rightDivided = true;
        rightDivide = index;
        break;
      };
    };
  };

  //선택, 미선택 부분을 넣기 위해
  //문자열을 선언해둡니다.
  string onSelect;
  string notSelect;

  //나눠진 상태면 나눠서 넣습니다.
  if (rightDivided)
  {
    onSelect = arg1.Substring(0, rightDivide).Trim();
    notSelect = arg1.Substring(rightDivide + 1, arg1.Length - rightDivide - 1).Trim();
  }
  //나눠지지 않았으면 선택에 다 넣습니다.
  else
  {
    onSelect = arg1;
    notSelect = "";
  };

  //선택지를 만듭니다.
  CreateSelection(arg0, onSelect, notSelect);
};

선택지는 형태가

 

[(선택지)?선택:미선택]

 

이런 모습이기 때문에

 

왼쪽은 괄호를 떼고

 

그대로 보관합니다.

 

 

오른쪽에서 ':'를 기준으로

 

문자열을 나눠서 보관합니다.

 

실제 실행은 선택할 때

 

할 것이기 때문에

 

지금은 문자열로만

 

보내도록 합시다.

 

 

만약 ':'가 없을 때에는

 

그냥 선택될 때 전체 지문

 

실행되도록 조정합니다.

 

 

 

 

이렇게 하면 드디어!

 

 

커맨드 분류가 모두 끝났네요!

 

선택지 부분만 조금 건들면

 

끝입니다~

 

 

선택지를 만듭시다!

private static void CreateSelection(string wantContext, string wantOnSelect, string wantNotSelect)
{
  //현재 생성된 선택지 개수가
  //최대치에 도달하지 않았을 때에만 추가합시다.
  if(currentSelectionNumber < currentSelection.Length)
  {
    //선택지 개수의 위치에 새로운 선택지를 만들면
    //선택지 맨 마지막 부분에 놓은 것과 마찬가지 효과를 낼 수 있습니다.
    currentSelection[currentSelectionNumber] = new SelectInformation(wantContext, wantOnSelect, wantNotSelect);

    //선택지 개수를 하나 늘려줍니다.
    ++currentSelectionNumber;
  };
}

SelectionInformation

 

이라는 것이 나오는데요

 

지문, 선택, 미선택

 

세 개의 변수로 이루어져 있어서

 

선택지 커맨드에 있는 것을

 

그대로 저장해주는 클래스입니다.

 

 

이걸 선택지 마지막 위치

 

생성해 주기만 하면

 

선택지 생성은 끝이죠~

 

 

선택지를 선택해봅시다.

private static void ChoiceSelection(int wantNumber)
{
  //읽기가 끝난 상태에서 받은 선택지 번호가
  //존재하는 선택지 범위 내에 있어야 실행합니다.
  if (!reading && wantNumber < currentSelectionNumber)
  {
    //모든 선택지를 돌면서 확인합니다.
    for(int index = 0; index < currentSelectionNumber; ++index)
    {
      //만약 선택지 누락이 발생하면 멈춥니다.
      if(currentSelection[wantNumber] == null)
      {
        break;
      };

      //유저가 고른 선택지가 맞다면
      if(index == wantNumber)
      {
        //선택 시 실행할 내용을 실행합니다.
        if(currentSelection[wantNumber].onSelectedAction != null)
        {
          CommandText(currentSelection[wantNumber].onSelectedAction);
        };
      }
      //다른 선택지들은
      else
      {
        //선택되지 않았을 때 실행할 내용을 실행합니다.
        if (currentSelection[wantNumber].notSelectedAction != null)
        {
          CommandText(currentSelection[wantNumber].notSelectedAction);
        };
      };

      //작업이 끝나면 선택지를 지웁니다.
      currentSelection[index] = null;
    };

    //선택지를 모두 지웠기 때문에
    //개수를 0으로 맞춥니다.
    currentSelectionNumber = 0;

    //바로 다음 지문으로 넘어가도록 합시다.
    StartRead(nextReadPage);
  };
}

선택은 모든 선택지를 확인해서

 

본인 번호와 일치하는지 본 후

 

선택 / 미선택 부분을 실행,

 

모든 선택지를 초기화한 뒤에

 

다음 문장을 실행해줍니다.

 

 

미리 다 만들어 두었더니

 

정말 편하네요!

 

 

 

마지막 이름 변경 함수는

 

그냥 이름을 바꿔주고

 

출력하는 것이 전부라

 

넘어가도록 하겠습니다~

 

 

 

 

<마치며>

 

간단하게 대화창만 만들려 했는데

 

커맨드가 들어가면서

 

생각보다 커지게 되었습니다.. ㅎㅎ

 

 

언젠가 스크립트 언어를

 

인식하는 기능을 넣을 때

 

도움이 좀 될 수도 있겠네요~

 

 

 

긴 글 읽어주셔서 감사드리고

 

다음 포스팅에서 뵙겠습니다!