<ALGORITHM>/NOTE

20210121(목)

CodeGrimie 2021. 1. 21. 21:06

오늘은 알고리즘 말고도 다양한 C 표준 함수에 대해서도 배웠다.

rand() 함수

Random의 약자로 { 0 ~ RAND_MAX } 범위에서 무작위 Int형 값을 반환하는 함수다.

 

C언어의 표준 라이브러리 중 하나인 <cstdlib> 라이브러리에 매크로로 최댓값이 정의되어 있는데

운영체제에 따라 이 최댓값의 크기가 다 다르다.

 

리눅스, OpenBSD는 { #define RAND_MAX 2147483647(INT_MAX) } 라 정의 되어있고,

마이크로소프트社의 윈도우는 { #define RAND_MAX 0x7fff(32767) } 라고 정의되어있다.

 

▼ 예시 코드

#include <cstdio>
#include <cstdlib>

int main()
{
    printf("RAND : %d\n", rand());

    return (0);
}

 

srand() 함수

Seed Random의 약자로 무작위 값을 반환하기 위한 Seed값을 설정하는 Void형 함수다.

 

마인크래프트에서 맵 시드 값 할 때 그 시드와 동일 단어로

씨앗(Seed)에서 줄기가 자라난다는 것에 착안해서 사용하는 프로그래밍 용어다.

 

이 Seed 값을 바꿔주면 무작위 값을 생성하는 씨앗이 바뀌었기 때문에

무작위 값이 생성되는 패턴이 바뀌게 된다.

 

▼ 예시 코드

#include <cstdio>
#include <cstdlib>

int main()
{
    srand(1324);
    printf("RAND : %d\n", rand());
    srand(4500);
    printf("RAND : %d\n", rand());

    return (0);
}

srand()함수에는 한 가지 숨겨진 기능이 있는데 srand()를 사용하면 rand() 호출 수가 초기화된다.

 

▼ 무작위지만 무작위 같지 않은 무작위 코드

#include <cstdio>
#include <cstdlib>

int main()
{
    srand(1324);
    printf("RAND : %d\n", rand());
    srand(1234);
    printf("RAND : %d\n", rand());

    return (0);
}

위의 코드를 실행하면 같은 숫자가 출력된다.

 

이런 문제 때문에 srand()는 특정 경우를 제외하고는 처음 한 번만 설정하는 것이 좋다.

기본적으로 rand()함수는 srand(1)이 적용되어 있다.

time() 함수

1970년 1월 1일 00시 00분(UTC) 부터 지금까지 초단위의 시간을 time_t값으로 반환하는 함수다.

유닉스 운영체제를 개발하기 위해서 만들어진 프로그래밍 언어인 만큼 C언어에서

시간의 기준은 유닉스가 만들어진 순간부터라고 암묵적인 약속이 되어있다.


단순히 시간만 받아올 때는 time(NULL)로 간단하게 사용할 수 있다.

 

▼ 억 단위의 초가 나올 것이다.

#include <cstdio>
#include <ctime>

int main()
{
    // 그냥 바로 쓸 때는 NULL 
    printf("%lld\n", time(NULL));
    
    // 어딘가 저장해야 할 때는 포인터 접근 대입
    time_t t;
    time(&t)
    printf("%lld\n", t);
    
    return (0);
}

time 함수는 매 초 달라지기 때문에 이걸 이용해서 프로그램이 구동될 때마다 무작위 시드 값을 설정할 수 있다.

 

▼ 프로그램을 실행할 때마다 시드 값이 바뀐다.

#include <cstdio>
#include <cstdlib>
#include <ctime>

int main()
{
    srand(time(NULL));
    printf("RAND : %d\n", rand());

    return (0);
}

주사위 게임

위의 함수들을 사용해서 간단한 주사위 게임을 만들었다.

원래 주어진 과제에서는 플레이어가 미리 돈과 베팅 금액, 홀짝을 먼저 정하면 컴퓨터가 주사위를 굴리는 거였다.

근데 주사위를 굴리고 나서 홀짝과 베팅하는 게 더 재밌을 거 같아서 수정했다.

 

▼ 전체 코드

#include <cstdio>
#include <clocale>
#include <cstdlib>
#include <ctime>

#include <windows.h>

#define _CRT_SECURE_NO_WARNINGS

typedef struct _Player {
    int _money;
    int _bet;
    int _choice;
}Player;

typedef struct _Computer {
    int _dice;
}Computer;

enum class EvenOdd {
    EVEN = 1,
    ODD = 2
};

int main()
{
    _wsetlocale(LC_ALL, L"Korean");

    // 윈도우 콘솔 핸들 가져오기
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
    WORD saved_attributes;

    // 현재 윈도우 콘솔 정보 저장
    GetConsoleScreenBufferInfo(hConsole, &consoleInfo);
    saved_attributes = consoleInfo.wAttributes;

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

    Player player = { 0, 0, 0 };
    Computer computer = { 0 };

    SetConsoleTextAttribute(hConsole, FOREGROUND_INTENSITY);
    wprintf(L"□□□□□□□□□□□□□□□□\n");
    wprintf(L"□□    ");
    SetConsoleTextAttribute(hConsole, saved_attributes);
    wprintf(L"$ 주사위 게임 $");
    SetConsoleTextAttribute(hConsole, FOREGROUND_INTENSITY);
    wprintf(L"     □□\n");
    wprintf(L"□□□□□□□□□□□□□□□□\n");
    SetConsoleTextAttribute(hConsole, saved_attributes);

    wprintf(L"▷ Set Money : ");
    scanf_s("%d", &(player._money));

    while (player._money > 0)
    {
        wprintf(L"〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓\n");
        wprintf(L"※ Current Money : %d\n", player._money);
        wprintf(L"〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓\n");

        wprintf(L"\n");
        wprintf(L"◇ ...\n");
        wprintf(L"◇ Computer Roll Dice..\n");
        computer._dice = (rand() % 6 + 1);
        wprintf(L"◇ ...\n");
        wprintf(L"\n");

        wprintf(L"▷ Even(2) or Odd(1) : ");
        scanf_s("%d", &(player._choice));

        wprintf(L"▷ Set Betting : ");
        scanf_s("%d", &(player._bet));

        wprintf(L"\n");

        wprintf(L"〓 Dice was %d\n", computer._dice);

        EvenOdd isEven = computer._dice % 2 ? EvenOdd::EVEN : EvenOdd::ODD;
        if (player._choice == static_cast<int>(isEven))
        {
            SetConsoleTextAttribute(hConsole, FOREGROUND_GREEN);
            wprintf(L"〓 Yeah!\n");
            SetConsoleTextAttribute(hConsole, saved_attributes);
            player._money += player._bet * 2;
            player._bet = 0;
        }
        else
        {
            SetConsoleTextAttribute(hConsole, FOREGROUND_RED);
            wprintf(L"〓 Shit!\n");
            SetConsoleTextAttribute(hConsole, saved_attributes);
            player._money -= player._bet;
            player._bet = 0;
        }
        player._choice = 0;
        wprintf(L"\n");
    }

    wprintf(L"〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓\n");
    wprintf(L"※ Current Money : %d\n", player._money);
    wprintf(L"〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓\n");

    return (0);
}

구조체, 열거체, 삼항 연산자까지 다양한 기능을 사용하려고 신경 썼다.

시간이 제법 남아서 추가로 윈도우 터미널에서 글자 색상을 바꾸는 것도 추가할 수 있었다.

 

나중 가서 입력 예외 처리를 안 해줬다는 사실을 깨달았다.

예외 처리를 꼭꼭 꼭 하자.

 

▼ 윈도우 터미널 색상 변경

#include <cstdio>
#include <windows.h>

int main()
{
    // 윈도우 콘솔 핸들 가져오기
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
    WORD saved_attributes;
    
    // 현재 윈도우 콘솔 정보 저장
    GetConsoleScreenBufferInfo(hConsole, &consoleInfo);
    saved_attributes = consoleInfo.wAttributes;
    
    // Text 속성 값 전체 출력
    for (int k = 1; k < 255; k++)
    {
        SetConsoleTextAttribute(hConsole, k);
        printf("Hello, World!\n");
        SetConsoleTextAttribute(hConsole, saved_attributes);
    }

    return (0);
}

16가지의 색만을 지원하지만 조합되면 경우의 수가 꽤 많다.

 

▼ 진짜 간단한 게임이라면 충분한(?) 색상

 

▼ 교수님이 만든 랜덤 함수

int random(int start, int end);

CPP, C#에서는 위의 형태를 많이 쓰기 때문에 형태에 익숙해지라고 위와 같이 만드셨다.

Pow() 함수

n제곱double형으로 반환하는 함수다.

 

▼ 예시 코드

#include <cstdio>
#include <cmath>

int main()
{
    printf("2    ^ 2 = %f\n", pow(2, 2));
    printf("2.0f ^ 2 = %f\n", pow(2.0f, 2.0f));

    return(0);
}

 

<cmath> 라이브러리 내부에서 매크로로 정의되어 있어서 int, float, double 모두 사용 가능하다.

단순히 제곱을 구하는 것이기 때문에 실제로 게임 개발에서는 그냥 { n * n }으로 직접 작성하는 경우가 많다.

abs(), fabs() 함수

n의 절댓값을 int형 혹은 double형으로 반환하는 함수다.

 

간단히 말하면 정수 형과 실수 형 두 경우를 나눠서 함수를 사용하는 것인데 각자 소속된 헤더가 다르다.

abs는 <cstdlib>에 위치하지만 fabs는 <cmath>에 위치한다.

 

이렇게 두 개로 나뉜 이유는 단순히 사용 빈도의 차이라고 한다.

abs는 fabs에 비해서 사용 빈도가 월등히 높고 fabs는 전문적인 수학 계산 외엔 쓰는 경우가 잘 없다.

C 표준 위원회는 이 한 개의 함수 조차 <cstdlib>에 두기엔 메모리가 아깝다고 <cmath>로 빼내었다.

 

▼ 예시 코드

#include <cstdio>
#include <cstdlib>
#include <cmath>

int main()
{
    // abs
    printf("abs(-int) : %d\n", abs(-1));
    printf("abs(+int) : %d\n", abs(1));

    //fabs
    printf("fabs(-double) : %f\n", fabs(-1.0f));
    printf("fabs(+double) : %f\n", fabs(1.0f));

    return(0);
}

 

제곱 공식 구현

위에서 배운 함수들을 이용해서 제곱 공식을 코드로 구현한다.

 

▼ 제곱 공식

(a + b)² = a² + 2ab + b²
(a - b)² = a² - 2ab + b²

▼ 예시 코드

// (a + b)² = a² + 2ab + b²
pow(a, 2) + (a * b * 2) + pow(b, 2)

// (a - b)² = a² - 2ab + b²
pow(a, 2) + (a * b * 2) + pow(b, 2)

수학식을 코드로 작성하기

이어서 간단한 수학식들을 코드로 바로 옮겨보는 연습을 진행했다.

고등학교 이후로 이렇게 다양한 수학식을 보는 건 처음이었다..

 

▼ 코드

#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <clocale>

int main()
{
    _wsetlocale(LC_ALL, L"Korean");

    int a = 5, b = -2, x = 0;

    // |a| ≥ 0
    if (abs(a) >= 0)
        wprintf(L"|%d| ≥ 0\n\n", a);

    // |a| = sqrt(a ^ 2)
    if (abs(a) == sqrt(pow(a, 2)))
        wprintf(L"|%d| = sqrt(%d ^ 2)\n\n", a, a);

    // |a ^ 2| = |a| ^ 2
    if (abs(pow(a, 2)) == pow(abs(a), 2))
        wprintf(L"|%d ^ 2| = |%d| ^ 2\n\n", a, a);
    
    // |a * b| = |a| * |b| (b ≠ 0)
    if (abs(a * b) == abs(a)*abs(b) && b != 0)
        wprintf(L"|%d * %d| = |%d| * |%d|\n\n", a, b, a, b);

    // |a / b| = |a| / |b|
    if (abs(a / b) == abs(a)/abs(b))
        wprintf(L"|%d / %d| = |%d| / |%d|\n\n", a, b, a, b);

    // |x| ≤ a 
    // -a ≤ x ≤ a
    x = (a - 1) * -1;
    if (abs(x) <= a)
    {
        wprintf(L"|%d| ≤ %d\n", x, a);
        wprintf(L"-%d ≤ %d ≤ %d\n\n", abs(x), a, abs(x));
    }

    // |x| ≥ a 
    // x ≤ -a || x ≥ a
    x = a + 1;
    if (abs(x) >= a)
    {
        wprintf(L"|%d| ≥ %d\n", x, a);
        wprintf(L"%d ≤ -%d || %d ≥ %d\n\n", x, abs(a), x, abs(a));
    }

    return (0);
}

 

(弧)도(Radian) 계산

각도를 표현하는 방법은 각도(Degree)와 호도(Radian), 회전수(Revolution)으로 세 가지 방법이 있다.

각도는 우리가 일상적으로 사용하는 60분법으로 n도로 표시하는 것인데 호도와 회전수는 낯설다.

 

호도 법은 호의 길이와 반지름 길이의 비례를 표시하는 방법이다.

그런 원리로 특정한 기준에서 몇 도라 표현하는 각도와 달리 호도는 순수한 값으로 존재한다.

 

▼ 각도와 호도의 관계

기본적으로 컴퓨터는 숫자로 모든 걸 계산하기 때문에

각도 법이나 회전수보다는 호도법이 훨씬 계산이 편하고 빠르다.

 

이러한 강점 때문에 특별한 언급이 없다면 거의 대부분의 코드들은 호도법으로 작성되어있다.

 

회전수는 말 그대로 몇 바퀴 회전하는 지를 세는 표현 방법이다.

게임에서 일반적으로 사용하지는 않지만 경우에 따라 변수명으로 가끔 사용되는 정도다.

 

▼ 기본적인 사용법

#include <cstdio>
#include <cstdlib>
#include <cmath>

#define PI_VALUE 3.1415926

int main()
{
    int degree = 45;

    double sinValue = sin(degree * (PI_VALUE / 180));
    double cosValue = cos(degree * (PI_VALUE / 180));
    double tanValue = tan(degree * (PI_VALUE / 180));

    printf("SIN(%d * PI / 180) : %f\n", degree, sinValue);
    printf("COS(%d * PI / 180) : %f\n", degree, cosValue);
    printf("TAN(%d * PI / 180) : %f\n", degree, tanValue);

    return (0);
}

컴퓨터가 연산하는 데는 호도가 최고지만 프로그래머가 빠르게 읽기에는 각도가 최고다.

그래서 각도를 호도로 변환하여 사용하는 경우가 많다.

 

360 각도 = 2π * 호도

각도에서 호도로 변환하는 것은 { 360° = 2π * 호도 } 이라는 것만 이해하면 된다.

1 호도는 대략 57도 정도가 된다.