본문 바로가기

Unity/Unity 스파르타

2주차, 배열과 리스트 고찰 + 콘솔을 이용한 틱택토 게임 만들기

728x90

목차

1. 배열과 리스트 고찰

2. 콘솔을 이용한 틱택토 게임 만들기

 

 

 

1. 배열과 리스트 고찰

배열리스트의 차이점을 크게 4가지로 나눠 알아 보겠습니다.

  1. 크기
    • 배열은 고정된 크기를 가지며, 한 번 생성되면 크기를 변경할 수 없습니다.
    • 리스트는 동적으로 크기를 조정할 수 있습니다. 따라서 리스트는 필요에 따라 요소를 추가하거나 제거할 수 있습니다.
  2. 데이터 유형
    • 배열은 동일한 데이터 유형의 요소들로 이루어져 있습니다. 예를 들어, 정수 배열은 정수 값들의 집합으로 구성됩니다.
    • 리스트는 다른 데이터 유형의 요소들을 혼합하여 저장할 수 있습니다. 예를 들어, 정수와 문자열을 함께 저장할 수 있는 리스트를 만들 수 있습니다.
    • 리스트는 Linked List*로 구현 되기 때문에 접근 속도가 배열보다 느립니다.
  3. 메모리 할당
    • 배열은 연속된 메모리 공간에 요소들을 저장합니다. 따라서 배열은 인덱스를 사용하여 특정 요소에 빠르게 접근할 수 있습니다.
    • 리스트는 연결된 노드로 구성되어 있으며, 각 노드는 데이터와 다음 노드를 가리키는 포인터를 포함합니다. 이로 인해 리스트는 배열보다 요소에 접근하는 데 더 많은 시간이 소요될 수 있습니다. 
    • 리스트는 동적으로 크기를 조정 하기 때문에 많은 메모리를 사용합니다.
  4. 기능
    • 배열은 주로 순차적인 데이터 접근이 필요한 경우에 사용됩니다.
    • 리스트는 데이터의 삽입, 삭제, 이동 등의 작업이 빈번한 경우에 유용합니다. 리스트는 동적인 데이터 관리에 유연성을 제공합니다.

# Linked List란 무엇인가. ⬇️

더보기

데이터 요소들을 노드(Node)라는 객체로 구성하여 연결된 구조로 저장하는 자료 구조입니다. 다음은 Linked List가 가지고 있는 특징 입니다.

노드(Node): 링크드 리스트를 구성하는 각 요소로, 데이터와 다음 노드를 가리키는 포인터로 이루어져 있습니다.

헤드(Head): 링크드 리스트의 첫 번째 노드를 가리키는 포인터입니다. 헤드를 통해 링크드 리스트에 접근할 수 있습니다.

테일(Tail): 링크드 리스트의 마지막 노드를 가리키는 포인터입니다. 테일은 링크드 리스트의 끝을 나타내며, 새로운 노드를 추가할 때 사용될 수 있습니다.

단일 연결 리스트(Singly Linked List): 각 노드가 다음 노드를 가리키는 단방향 링크드 리스트입니다. 각 노드는 다음 노드를 가리키는 포인터를 가지고 있으며, 마지막 노드의 포인터는 NULL로 표시됩니다.

이중 연결 리스트(Doubly Linked List): 각 노드가 이전 노드와 다음 노드를 가리키는 양방향 링크드 리스트입니다. 각 노드는 이전 노드와 다음 노드를 가리키는 포인터를 가지고 있으며, 리스트의 양쪽 끝에는 헤드와 테일이 존재합니다.


 

요약

리스트는 동적으로 크기를 조정할 수 있으며 데이터를 관리하는데 편리하고 유연한 장점을 가지고 있습니다.

그러나 특정 인덱스에 직접 접근하기 위해서는 연결된 노드들을 순차적으로 탐색해야 한다는 단점이 있습니다. 이로 인해 배열보다는 접근 속도가 느릴 수 있습니다.

리스트에서 특정 인덱스를 찾기 위해서는 처음부터 순서대로 노드를 탐색해야 하므로 최악의 경우 O(n)의 시간 복잡도를 가집니다. 배열에서는 인덱스를 통해 직접 접근이 가능하기 때문에 O(1)의 시간 복잡도를 가지는 것과 비교하면 상대적으로 느릴 수 밖에 없습니다.

하지만 리스트는 데이터의 삽입과 삭제가 배열에 비해 간단하고 효율적입니다. 삽입 또는 삭제하고자 하는 위치의 앞 또는 뒤에 노드를 삽입하거나 삭제하는 연산은 O(1)의 시간 복잡도를 가집니다. 이는 배열에서 요소를 삽입 또는 삭제할 때 데이터를 이동시켜야 하는 경우에 비해 리스트가 효율적이라는 것을 의미합니다.따라서, 리스트는 데이터의 삽입과 삭제가 빈번하게 발생하고 특정 인덱스를 빈번하게 찾아야 하는 상황에서 유용하게 사용될 수 있습니다. 그러나 특정 인덱스에 직접 접근해야 하는 경우에는 배열이 더 효율적일 수 있습니다. 적절한 상황에 맞게 자료 구조를 선택하고 사용하는 것이 중요합니다.

 

 

2. 콘솔을 이용한 틱택토 게임 만들기

틱택토 게임이란

틱택토는 두 명의 플레이어가 번갈아가며 3x3 크기의 게임 보드에 마크를 놓는 전략 게임입니다.

목표는 자신의 마크를 가로, 세로, 대각선 방향으로 연속으로 3개를 놓는 것입니다.

게임 보드가 가득 차거나 더 이상 연속된 3개의 마크를 놓을 수 없는 경우 무승부가 됩니다.

 

1. 필요한 변수들을 만들어 봅니다.

//game State
bool gameEnd = false;
bool firstTurn = true;
int boardCheckCount = 0;
string firstMark = "A";
string secondMark = "B";
//3*3 보드판 만들기
string[,] board = new string[3, 3];

2. 보드판을 만들어줍니다.

void BoardReset() //보드판 리셋 처음 시작시 실행
{
    boardCheckCount = 0;
    for(int i = 0; i< 3; i++)
    {
        Console.Write((3-i) + "|\t");
        for(int j = 0; j < 3; j++)
        {
            board[i, j] = "O";
            Console.Write(board[i, j]+"\t");
        }
        Console.WriteLine();
    }
    Console.Write("--------------------------\n");
    Console.Write(0 + "|\tA\tB\tC\n");
}

3. 콘솔로 사용자의 좌표를 받아와 옳은 좌표값인지 확인하고 반환하는 함수를 만들어 줍니다.

  • 보드판의 좌표를 보기쉽게 ABC, 123으로 그려놨기 때문에 해당 좌표를 받으면 옳은 배열로 변환해 줘야합니다. Split으로 받은 배열을 , 단위로 나눠줍니다.
  • TryParse를 사용해 변환이 가능한지, 변환이 된다면 out으로 값을 보내줍니다. x좌표는  소문자 대문자 상관없이 받아준뒤 대문자로 변환해줍니다. 그리고 스위치로 대문자를 위치값으로 바꿔줍니다. 콘솔에 y좌표는 1부터 시작하기 때문에 1이상 3이하인 수면 -1해서  coords 배열에 넣어줍니다.
  • 받은 배열에  보드판이 기본 디폴트 값인 "O" 이 아니라면 다시 처음으로 돌아가줍니다.
  • 조건들을 모두 만족하면 위치값을 반환해줍니다.
int[] InitBoardPosition(string[,] board) //보드에 좌표 입력, 옳은 좌표면 좌표값 반환 2
{
    int[] coords = new int[2];
    
    while (true)
    {
        Console.WriteLine("카드를 놓을 좌표를 정확히 골라주세요. ex1) A,2 ex2) C,3");
        string[] coord = Console.ReadLine().Split(",");

        if(coord.Length ==2 && char.TryParse(coord[0],out char coordX) && int.TryParse(coord[1],out int coordY))
        {
            if(((coordX>='a' && coordX<='c') || (coordX>='A' && coordX <= 'C')) && (coordY>=1 && coordY <=3))
            {
                coordX = char.ToUpper(coordX);
                coords[0] = 3-coordY;
                switch (coordX)
                {
                    case 'A':
                        coords[1] = 0;
                        break;
                    case 'B':
                        coords[1] = 1;
                        break;
                    case 'C':
                        coords[1] = 2;
                        break;
                }
                if (board[coords[0],coords[1]] != "O")
                {
                    continue;
                }
                return coords;
            }
        }
    }
}

4. 게임 승리 조건은 자신의 마크를 가로, 세로, 대각선 방향으로 연속으로 3개를 놓는 것 이기 때문에 이에 맞는 알고리즘을 만들어 줍니다.

  • 3*3 이기 때문에 단순하게 로직을 짜줍니다.
  • 처음엔 가로 방향을 탐색합니다. 가운데를 기준으로 양옆에 마크가 똑같으면 true 를 반환해줍니다.
  • 세로방향도 똑같이 탐색해줍니다.
  • 각각 대각선도 확인해 줍니다.
  • 조건에 보드판의 기본 디폴트 값인 "O" 이 아닌 경우도 넣어줘야 합니다.
bool CheckCondition(string[,] board)
{
        for (int i = 0; i < 3; i++)
        {
            string term = board[i, 1];
            if(term == board[i,0] && term == board[i, 2] && term != "O")
            {
                return true;
            }
        }

    for (int i = 0; i < 3; i++)
    {
        string term = board[1, i];
        if(term == board[0,i] && term == board[2, i] && term != "O")
        {
            return true;
        }
    }

    if (board[0, 0] == board[1,1] && board[1,1] == board[2, 2] && board[1,1] != "O")
    {
        return true;
    }

    if (board[2, 0] == board[1, 1] && board[1, 1] == board[0, 2] && board[1,1] != "O")
    {
        return true;
    }

    
    return false;
}

 

5. 필요한 변수와 함수를 만들었으니 게임을 만들어 봅니다.

  • 처음 시작때 보드판은 리셋시켜주고 보드판을 콘솔에 찍어줍니다.
  • 게임이 시작되면 위에만든 함수를 사용해 좌표를 받아줍니다.
  • 어떤 마커가 먼저 시작인지 정해줍니다.
  • 받은 좌표를 보드판에 적용시켜주고 콘솔창에 보여줍니다.
  • 위에 만든 승리조건 확인 함수를 사용해 게임의 종료를 판단해줍니다.
  • 게임을 다시하기를 누르면 보드판을 리셋해주고 컨티뉴를 해줍니다.
  • 만약 보드판이 가득찼는데 승부가 안났다면 무승부 처리해주고 다시시작 유무를 확인해줍니다.
BoardReset();

while (true)
{
    gameEnd = false;
    string turn = "";
    int[] coords = new int[2];
    Console.WriteLine("첫번째 시작 돌 = A, 두번째 시작 돌 = B");
    coords = InitBoardPosition(board); //좌표 받기

    if (firstTurn)
    {
        boardCheckCount++;
        firstTurn = false;
        turn = firstMark;
    }
    else
    {
        boardCheckCount++;
        turn = secondMark;
        firstTurn = true;
    }

    BoardSet(coords[0], coords[1], turn);
    if (CheckCondition(board))
    {
        Console.WriteLine("게임 끝!");
        Console.WriteLine("1) 다시하기  2) 게임 종료");
        string str = Console.ReadLine();

        if(str == "1")
        {
            gameEnd = true;
            BoardReset();
            continue;
        }
        else
        {
            break;
        }
    }

    
  if(boardCheckCount == 9 && !gameEnd)
    {
        Console.WriteLine("무승부!");
        Console.WriteLine("1) 다시하기  2) 게임 종료");
        string str = Console.ReadLine();
        if (str == "1")
        {
            BoardReset();
            continue;
        }
        else
        {
            break;
        }
    }

}

완성

 

 

728x90