20210121(목)
오늘은 알고리즘 말고도 다양한 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도 정도가 된다.