N Log

가위, 바위, 보

문제

다음 조건을 만족하는 가위바위보 게임을 작성하시오.

사용자는 바위, 가위, 보, 종료 중 하나를 숫자로 입력한다.
게임은 사용자가 종료를 입력할 때까지 반복된다.

컴퓨터는 가위, 바위, 보 중 하나를 임의로 선택한다.
사용자의 선택과 컴퓨터의 선택을 비교하여 승리, 패배, 무승부를 출력한다.

유효하지 않은 값을 입력하면 유효하지 않은 입력임을 출력하고 다시 입력을 받는다.

풀이

가위, 바위, 보는 반복문과 조건문으로 풀 수 있는 단순한 문제이다.
하지만 가독성을 위해 enum을 도입하거나, 승패를 판단하는 규칙을 더 명확하게 정리해 볼 여지가 있는 문제이기도 하다.

하드 코딩

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void)
{
    srand((unsigned int)time(NULL));

    while (1)
    {
        int user_choice;

        printf("Rock(1), Scissors(2), Paper(3), Exit(0): ");

        if (scanf("%d", &user_choice) != 1)
        {
            int buffer_char;

            while ((buffer_char = getchar()) != '\n' && buffer_char != EOF)
                ;

            printf("Please enter a number.\n");
            continue;
        }

        if (user_choice == 0)
            break;

        if (user_choice < 1 || user_choice > 3)
        {
            printf("Invalid choice. Please choose 1, 2, or 3.\n");
            continue;
        }

        int computer_choice = rand() % 3 + 1;

        printf("User: ");
        if (user_choice == 1)
            printf("Rock");
        else if (user_choice == 2)
            printf("Scissors");
        else
            printf("Paper");

        printf(", Computer: ");
        if (computer_choice == 1)
            printf("Rock");
        else if (computer_choice == 2)
            printf("Scissors");
        else
            printf("Paper");
        printf("\n");

        if (user_choice == 1)
        {
            if (computer_choice == 1)
                printf("Draw.\n");
            else if (computer_choice == 2)
                printf("You win.\n");
            else
                printf("You lose.\n");
        }
        else if (user_choice == 2)
        {
            if (computer_choice == 1)
                printf("You lose.\n");
            else if (computer_choice == 2)
                printf("Draw.\n");
            else
                printf("You win.\n");
        }
        else
        {
            if (computer_choice == 1)
                printf("You win.\n");
            else if (computer_choice == 2)
                printf("You lose.\n");
            else
                printf("Draw.\n");
        }
    }

    return 0;
}

입력값을 0, 1, 2, 3 같은 숫자 리터럴만으로 다루면 user_choice == 2를 보았을 때 2가 무엇을 의미하는지 바로 떠올리기 어렵다.
또한 지금 코드는 사용자가 가위인 경우, 바위인 경우, 보인 경우를 각각 나누고, 그 안에서 컴퓨터의 선택을 다시 비교해 승패를 판단한다.
즉 승패 판단에 필요한 경우의 수를 일일이 나열하는 형태라 코드가 길어지고 읽기 어려워진다.

enum 활용

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

enum Choice
{
    CHOICE_EXIT = 0,
    CHOICE_ROCK = 1,
    CHOICE_SCISSORS = 2,
    CHOICE_PAPER = 3
};

int main(void)
{
    srand((unsigned int)time(NULL));

    while (1)
    {
        int input_value;

        printf("Rock(1), Scissors(2), Paper(3), Exit(0): ");

        if (scanf("%d", &input_value) != 1)
        {
            int buffer_char;

            while ((buffer_char = getchar()) != '\n' && buffer_char != EOF)
                ;

            printf("Please enter a number.\n");
            continue;
        }

        if (input_value == CHOICE_EXIT)
            break;

        if (input_value < CHOICE_ROCK || input_value > CHOICE_PAPER)
        {
            printf("Invalid choice. Please choose 1, 2, or 3.\n");
            continue;
        }

        enum Choice user_choice = (enum Choice)input_value;
        enum Choice computer_choice = (enum Choice)(rand() % 3 + 1);

        printf("User: ");
        if (user_choice == CHOICE_ROCK)
            printf("Rock");
        else if (user_choice == CHOICE_SCISSORS)
            printf("Scissors");
        else
            printf("Paper");

        printf(", Computer: ");
        if (computer_choice == CHOICE_ROCK)
            printf("Rock");
        else if (computer_choice == CHOICE_SCISSORS)
            printf("Scissors");
        else
            printf("Paper");
        printf("\n");

        if (user_choice == computer_choice)
            printf("Draw.\n");
        else if ((user_choice == CHOICE_ROCK && computer_choice == CHOICE_SCISSORS) ||
                 (user_choice == CHOICE_SCISSORS && computer_choice == CHOICE_PAPER) ||
                 (user_choice == CHOICE_PAPER && computer_choice == CHOICE_ROCK))
            printf("You win.\n");
        else
            printf("You lose.\n");
    }

    return 0;
}

enum을 사용하면 숫자 리터럴 대신 의미 있는 이름으로 값을 비교할 수 있다.

Lookup Table 활용

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define INPUT_EXIT 0

enum Hand
{
    HAND_ROCK,
    HAND_SCISSORS,
    HAND_PAPER,
    HAND_COUNT
};

int main(void)
{
    const char* hand_names[HAND_COUNT] = {
        "Rock",
        "Scissors",
        "Paper"
    };

    const char* result_table[HAND_COUNT][HAND_COUNT] = {
        /*               Rock         Scissors     Paper */
        /* Rock */     {"Draw.",    "You win.",  "You lose."},
        /* Scissors */ {"You lose.", "Draw.",    "You win."},
        /* Paper */    {"You win.",  "You lose.", "Draw."}
    };

    srand((unsigned int)time(NULL));

    while (1)
    {
        int input_value;

        printf("Rock(1), Scissors(2), Paper(3), Exit(0): ");

        if (scanf("%d", &input_value) != 1)
        {
            int buffer_char;

            while ((buffer_char = getchar()) != '\n' && buffer_char != EOF)
                ;

            printf("Please enter a number.\n");
            continue;
        }

        if (input_value == INPUT_EXIT)
            break;

        int hand_index = input_value - 1;

        if (hand_index < HAND_ROCK || hand_index > HAND_PAPER)
        {
            printf("Invalid choice. Please choose 1, 2, or 3.\n");
            continue;
        }

        enum Hand user_hand = (enum Hand)hand_index;
        enum Hand computer_hand = (enum Hand)(rand() % HAND_COUNT);

        printf("User: %s, Computer: %s\n",
               hand_names[user_hand],
               hand_names[computer_hand]);
        printf("%s\n", result_table[user_hand][computer_hand]);
    }

    return 0;
}

가위바위보는 사용자의 선택 3가지와 컴퓨터의 선택 3가지를 조합하면 가능한 경우의 수가 9개로 정해진다.
따라서 각 조합의 결과를 표처럼 정리할 수 있고, 이를 2차원 배열에 미리 저장해 두면 인덱스로 바로 조회할 수 있다.

사용자 입력은 1, 2, 3으로 받지만 result_table 배열은 0부터 시작하는 인덱스를 사용한다.
그래서 input_value - 1을 통해 입력값을 0, 1, 2로 변환한 뒤 배열의 인덱스로 사용한다.
패 이름도 마찬가지로 배열에서 조회한다.
덕분에 조건문으로 길게 나열하던 코드를 훨씬 간결하게 만들 수 있다.