우분투에서 C++ 개발하기(3) - CMake

앞선 시리즈 :
우분투에서 C++ 개발하기(1) - https://ladofa.blogspot.com/2018/07/c-1.html
우분투에서 C++ 개발하기(2) - https://ladofa.blogspot.com/2020/08/c-2.html
여기서 사용하는 예제는 두 번째 시리즈에서 사용한 예제를 그대로 사용한다. 예제를 몰라도 읽는데 문제는 없다.

-------------------------

1편과 2편을 잘 정독했던 분들이라도 여기서부터는 예제가 복잡해서 이해하기 쉽지 않다. 쉬운 예제로 설명하고 싶은데.. 불가능하다. cmake가 원래 복잡한 컴파일 과정을 간단하게 하는 거라서... 여기서부터는 구경만 해도 좋고, 어쩌다 CMakeLists.txt 를 분석할 일이 있으면 그 때 한 번 더 봐주시길...

-------------------------


리눅스에서 C++ 코드를 컴파일하려면 최종적으로는 gcc나 다른 컴파일러를 이용해서 컴파일 명령을 내려줘야 한다. 그러나 파일 개수가 많고 의존성이 방대한 큰 프로젝트에서 일일이 gcc 명령을 내릴 수는 없다. 이를 보완하기 위해 Makefile을 작성해서 컴파일 명령어를 미리 입력해두면 나중에는 별 다른 수고 없이 간단한 명령으로 컴파일이 가능하다. 그런데 Makefile을 작성할 때도 결국은 gcc를 잘 알고 있어야 하고, 각종 옵션과 명령어를 직접 입력하는 것도 힘든 일이다.

CMake 기본


CMake는 이런 작업들조차 단순화하여 간단한 명령어를 통해 입력 가능하도록 돕는다. CMake는 사용자가 작성한 스크립트를 해석해서 Makefile을 만든다. 결국 cmake 를 실행한 뒤 make를 다시 실행해야 한다.

CMake도 프로그램이기 때문에 안 깔려 있으면 직접 깔아야 하는데 우분투에서는 다음과 같이 패키지를 통한 설치를 지원한다.

$sudo apt install cmake


여기서 CMake의  apt 패키지 이름이 cmake이고, 실행 프로그램의 이름도 cmake이다. 터미널에 그냥 cmake라고 입력해보자.

Usage

  cmake [options] <path-to-source>
  cmake [options] <path-to-existing-build>
  cmake [options] -S <path-to-source> -B <path-to-build>

Specify a source directory to (re-)generate a build system for it in the
current working directory.  Specify an existing build directory to
re-generate its build system.

Run 'cmake --help' for more information.

위와 같이 뜨면 성공이다. 컴퓨터에 깔려 있는 cmake 프로그램을 실행한 것이다. 버전을 확인하고 싶으면 다음과 같이 입력한다.

$cmake --version

cmake가 인식하는 스크립트 파일의 이름은 CMakeLists.txt 로 정해져있다. make가 기존에 존재하고 있는 Makefile 을 자동으로 인식하는 것처럼 cmake 역시 CMakeLists.txt 라는 이름의 파일을 찾는다.

이전 시리즈에서 작업했던 것과 같이 my.cpp, my.h, main.cpp 파일이 있다고 가정할 때, 이를 빌드하기 위한 CMakeLists.txt 스크립트는 다음과 같다.


cmake_minimum_required (VERSION 3.10)
project (mytest)
add_executable (mytest my.cpp main.cpp)

맨 첫째 줄은 cmake의 최소 버전을 밝히는 것이다. 해당 버전보다 아래인 cmake로 실행할 수  없게끔 막는다.
project()는 해당 스크립트의 프로젝트 이름을 밝힌다. mytest는 이 프로젝트의 이름이며 실행파일을 생성할 경우 실행파일의 이름이 될 것이다.
add_executable은 이 스크립트를 통해 최종적으로 실행파일을 만들고자 한다는 뜻이다. 실행파일 말고 정적 라이브러리를 만들고 싶을 때는 add_library라는 명령을 이용한다.

이 스크립트를 실행하려면 다음과 같이 입력하면 되는데...

$cmake .

make 는 무조건 실행한 경로에서 Makefile의 존재유무를 찾지만 cmake는 반드시 CMakeLists.txt의 경로를 지정해줘야 한다. cmake 다음에 나오는 점(.)이 바로 경로를 나타내는 것이다. 점 하나 찍으면 현재 경로를 말한다.

cmake는 Makefile을 생성하면서 기타 다양한 파일들도 생성하므로 그냥 현재 폴더에서 실행하면 이런저런 파일이 생겨 지저분해진다. 그래서 보통은 build 디렉토리를 만들어서 거기서 실행한다.

$mkdir build
$cd build
build$cmake ..

위와 같이 build 디렉토리를 만든 뒤, 해당 디렉토리 내에서 cmake를 실행한다. 이 때 cmake 뒤에는 점 두개 (..) 가 붙어 있다. 이는 상위 디렉토리를 나타낸다. CMakeLists.txt 파일이 build디렉토리에 있지 않고 그 상위 디렉토리에 있기 때문이다.

빌드에 성공하면 이런 저런 메타파일이 생성되고, 가장 중요한 Makefile이 만들어진다. 이 Makefile의 타겟은 기본적으로 all 과 clean을 가지고 있다. 타깃을 입력하지 않으면 all을 기본으로 한다. 아래와 같이 입력해보자.

build$make

그냥 make라고 하면 자동 생성된 Makefile을 실행시킨다. 여기까지는 거의 공식처럼 사용한다.



빌드에 성공하면 mytest 파일이 만들어져 있다. mytest를 실행해보자.

build$./mytest
3^2 == 9

이제 main.cpp파일을 수정할 것이다. 다음 프린트 구문을 추가한다.

printf("This code is modified.\n" );

다시 mytest를 빌드해서 실행해보자.

build$make
build$./mytest
3^2 == 9
This code is modified.

위와 같이 수정되서 나타난다. 뭔가 신기한 일이 이뤄진 것 같지만 사실 별 것도 아니다. 여기서 일단 CMake는 하는 일이 없다. make를 실행하면 수정된 파일을 감지해서 다시 빌드를 해준다. 그리고 그 결과가 나타난 것 뿐이다.

이제 your.h, your.cpp 파일을 추가할 것이다.

<your.h>
int your_func(int x);

<your.cpp>
#include "your.h"

int your_func(int x)
{
    return x + 10;
}


main.cpp에서도 your_func 을 사용하도록 수정한다.

<main.cpp>
#include <iostream>
#include "my.h"
#include "your.h"

int main(void)
{
    printf("%d^2 == %d\n", 3, my_func(3));
    printf("This code is modified.\n" );
    printf("%d + 10 == %d\n", 3, your_func(3));
}


마지막으로 CMakeLists.txt파일을 다음과 같이 수정한다.

cmake_minimum_required (VERSION 3.10)
project (mytest)
add_executable (mytest my.cpp main.cpp your.cpp)


add_executable에 your.cpp 를 추가한 것이다. 이제 아까와 마찬가지로 새로 빌드를 해본다.

build$make
build$./mytest
3^2 == 9
This code is modified.
3 + 10 == 13

수정된 결과가 반영되어 있다. cmake는 수정된 CMakeLists.txt 파일을 자동으로 반영하여 Makefile을 미리 바꿔놓았다. 그리고 나는 make명령어를 통해  바뀐 Makefile을 실행해서 결과를 확인했다. 

만약 CMakeLists.txt에 오류가 있으면 어떻게 될까?

cmake_minimum_required (VERSION 3.10)
project (mytest)
add_executable (mytest my.cpp main.cpp your.cpp nobody.cpp)

build$make

나는 make를 실행했는데, 실행해보면 CMakeLists.txt파일이 잘못되었다는 에러 메시지가 나온다. cmake는 건들지도 않았는데!

make는 어떻게 cmake를 인식하고 일반적인 빌드 오류가 아닌 cmake 오류를 출력하는 것일까? make가 cmake와 뭔가 연동되어 있는 것일까? Makefile 내부를 보면 cmake 에러가 없는지 확인하고 메시지를 출력하도록 되어 있다. cmake가 Makefile을 생성하면서 그 속에 cmake관련 내용을 스파이처럼 넣어놨다. 그래서 make만 실행해도 CMakeLists.txt의 오류를 검사하게 된다.


라이브러리 구조화

이제 my 라이브러리를 분리해서 컴파일해보자. 보통 프로그램 개발에서 핵심적인 기능을 담당하는 모듈은 기본 프로그램과 분리해서 따로 라이브러리 형식으로 개발하고, 본 프로그램은 해당 라이브러리를 사용하도록 만든다. 지금 예제에서는 mylib 라는 이름의 라이브러리를 개발하는 것으로 가정할 것이다.

일반적으로 라이브러리를 포함한 구조는 다음과 같이 되어 있다. 이 구조를 무조건 지킬 필요는 없지만 보통 이렇다..

  - 라이브러리 이름
    - src
      - files.cpp
    - include
      - files.h
    CMakeLists.txt
  - 또 다른 라이브러리
    ......
main.cpp
CMakeLists.txt

이렇게 글씨로 쓰면 구조가 안 보일까봐 VSCode 캡쳐 화면도 준비했다.



라이브러리마다 CMakeLists.txt 파일이 따로 있고 여기에는 src 디렉토리와 include 디렉토리가 있다. 빌드를 마치고 나서 src 폴더에 있는 cpp파일은 당연히 소스코드니까 공개하지 않지만 include 폴더에 있는 h, hpp 파일은 라이브러리를 사용하는 측에게 공개해야 한다. C의 라이브러리들은 헤더파일이 있어야 쓸 수 있다. 그런 이유를 포함해서 관리상의 편리함 등 여러 가지 이유로 src와 include는 분리해놓는 것이다.

혹시나 다른 사람이 만든 C언어 소스를 보게 되면 항상 include 디렉토리와 src 디렉토리가 따로 나뉜 걸 보게 된다. 또한 src를 컴파일한 결과는 bin 혹은 build 디렉토리에 모셔놓게 된다. 만약 소스코드가 아닌 빌드된 라이브러리를 다운받게 되면 include 속에 있는 헤더파일과 bin디렉토리 내부의 컴파일된 바이너라 파일만 보게 된다. 왜 그런지를 이해하려면 1편을 참고한다...


하여튼 mylilb 속에 있는 CMakeLists.txt의 내용은 다음고 같다.


cmake_minimum_required (VERSION 2.8)
project (mylib)
add_library (mylib src/my.cpp)
target_include_directories(mylib PUBLIC include)


아까의 CMakeLists.txt와 비교해서 다른 점은 add_executable 대신 add_library로 바뀌어 있다는 것이다. 실행파일을 만들지 않고 라이브러리를 생성한다는 뜻이다. 여기에는 사용되는 모든 cpp파일을 적어주어야 한다. 그리고 target_include_directories가 추가되었는데, 여기에 include 디렉토리를 알려줘야 한다.

다음과 같이 빌드해볼 수 있다.

mylib$mkdir build
mylib$cd build
mylib/build$cmake ..
mylib/build$make

빌드하고 나면 libmylib.a 파일이 생성되었다. 이것을 메인에서 이용하면 된다. 바깥쪽에 있는 CMakeLists.txt의 내용은 다음과 같다.

cmake_minimum_required (VERSION 2.8)
project (mytest)
add_executable (mytest main.cpp)
target_link_libraries(mytest PUBLIC mylib)
target_link_directories(mytest PUBLIC mylib/build)
target_include_directories(mytest PUBLIC mylib/include)

target_link_libraries에 라이브러리 이름을 추가한다. 라이브러리 이름이란 *.a에서 맨 앞에 lib를 제외한 것이다. gcc빌드할 때와 마찬기자로 라이브러리 파일은 맨 앞에 lib가 prefix로 붙는다. 파일 확장자가 앞쪽에 붙었다고 생각하면 된다.

target_link_directories 에는 라이브러리 파일의 경로를 추가한다. target_include_directorie 에는 헤더 파일의 경로를 추가하면 된다.

사실 이런 식으로 하는 것은 아예 동떨어진 다른 라이브러리를 가져오는 것과 동일하다. 그런데 현재 디렉토리/프로젝트 구조는 mylib 를 포함하고 있는데, 이 특징을 전혀 살리지 않은 것이다. 하위 디렉토리에 라이브러리가 들어있는 상황에서는 조금 더 간결하게 cmake명령을 만들 수 있다.

이제 add_subdirectory 를 이용해서 디렉토리를 구조화한 보람을 찾아보자.  mylib 디렉토리 내에 있는 CMakeLists.txt는 그대로 두고 메인 프로그램의 CMakeLists.txt를 다음과 같이 수정한다.

cmake_minimum_required (VERSION 2.8)
project (mytest)
add_subdirectory(mylib)
add_executable (mytest main.cpp)
target_link_libraries(mytest PUBLIC mylib)

add_subdirectory에 mylib를 추가했다. 이렇게 하면 mylib 디렉토리 속에 있는 CMakeLists.txt를 인식하고 가져온다. 라이브파일의 경로나 헤더 파일의 경로는 따로 추가할 필요가 없다. 다만 target_link libraries에 mylib를 추가하면 끝이다.

이렇게 하고 build 디렉토리를 만들어서 cmake를 해보면 mylib와 mytest가 같이 만들어지게 된다. 즉 여러 개의 CMakeLists.txt에 각각 명령어를 실행할 필요 없이 한 번에 연쇄적으로 실행된다. 실제로 build 내부에 mylib 디렉토리가 따로 생성되며 여기에 빌드된 라이브러리가 들어 있다.



외부 패키지 추가


마지막으로 외부 패키지를 찾아보도록 하자. 다른 사람이 빌드한 라이브러리를 추가할 때 위에서 소개한 바와 같이 include 디렉토리와 라이브러리를 일일이 추가하는 것은 상당히 번거롭다. 때문에 누군가 고수님께서 우리를 위해 라이브러리를 만드실 때는 CMake를 활용해서 자기 라이브러리를 잘 추가할 수 있도록 메타 정보를 같이 만들어둔다. 우리는 해당 메타 정보만 읽어들이면 라이브러리 추가에 필요한 각종 파라미터(include디렉토리, lib 디렉토리 등)를 자동으로 얻을 수 있다.

대표적인 라이브러리로 OpenCV가 있다. OpenCV를 본 프로젝트에 추가하려면 CMakeLists.txt에 다음과 같이 입력한다.


cmake_minimum_required (VERSION 2.8)
project (mytest)
find_package(OpenCV REQUIRED)
add_subdirectory(mylib)
add_executable (mytest main.cpp)
target_link_libraries(mytest PUBLIC mylib ${OpenCV_LIBS})
target_include_directories(mylib PUBLIC ${OpenCV_INCLUDE_DIRS})


find_package는 주어진 이름의 패키지(메타정보)를 찾아서 라이브러리의 목록과 헤더 파일의 디렉토리 목록을 변수에 저장한다. OpenCV 패키지는 어떻게 찾는가? 그 방법에 대한 링크가 있다.


대략 9가지 정도의 방법으로 패키지를 찾는다(...) 보통은 라이브러리 설치할 때 sudo make install 과 같은 명령어를 입력하면 그 속에 CMake 패키지 등록 과정이 포함되어 있다.

라이브러리를 찾는데 성공하면 OpenCV_LIBS와 OpenCV_INCLUDE_DIRS 가 변수로 제공된다. 이것을 타겟 빌드하는데 추가해주면 된다. 다른 라이브러리인 경우 OpenCV 자리에 다른 이름을 넣으면 된다.

만약 cmake가 없었다면? 우리는 OpenCV를 활용하기 위해 OpenCV 헤더 정보가 있는  include디렉토리를 직접 찾아서 입력해야 하고, 라이브러리 파일도 직접 찾아서 (아마 Makefile에) 추가해줘야 했을 것이다. 라이브러리 버전이 바뀌거나 업데이트되면? 또 달라진 경로와 파일을 일일이 수정해줘야 한다. 이 과정을 cmake가 알아서 해준다.

여기까지 와서 왜 라이브러리와 인클루드 디렉토리를 추가해야 하는가? 이런 의문이 들면 C컴파일 방법을 처음부터 다시 배워야 한다. 이글 맨 위의 링크에서 1탄으로 가시기 바란다.

참고로 CMake 스크립트에서는 순서가 중요하다. target_xxx 인 명령어는 add_executable이나 add_library 뒤에 와야 한다. 그 밖의 명령어는 add_executable/add_library 앞에 오면 된다. 제일 중요한 부분인데.... 마지막에 밝힌다.



  이상으로 우분투에서 C+ 개발하기 시리즈를 마친다.

--------

VSCode로 빌드하기... 는 그냥 연재 안 하는 것으로...  검색해보면 다른 글이 많으니 굳이 내가 수고할 필요가 없다. Remote SSH로 연결해서 컴파일하고, 디버그하고.. 그런 내용일 것이다.
여러분들이 라즈베이파이, 어디 서버 등 리눅스에서 돌릴 프로그램을 개발한다면 파이썬이든 C++ 이든 무조건 VSCode의 Remote SSH 기능을 활용하는 것을 추천한다.

--------

제 블로그에서 가장 인기있는 글이 바로 여기 시리즈라서 따로 인사를 남깁니다. 방문하신 모든 분들, 댓글 달아주신 분들 감사합니다.


Read More

롤 설치 에러 (004 혹은 업데이트의 파일 일부가 손상)



간만에 롤을 깔아보려니 이상한 에러가 자꾸 뜬다.

이 업데이트의 파일 일부가 손상되거나 사라졌습니다. 재시작해 다시 시도하거나 재설치해주세요. 문제가 계속 발생할 경우 고객지원에 문의해 주세요.

라는 메시지인데, 고객지원을 눌러봐야 영 엉뚱한 얘기만 써 있고..


근데.. 어쩌다 해결이 됐다..??
참 이상한데

1. 바탕화면에 깔린 아이콘에 오른쪽을 눌러서 관리자 권한으로 실행

2. 업데이트

반복..?

한 30번 정도 반복하니까 어쩌다 됐다. ?

그런데 클라이언트 실행하면 또 004 에러.. 여기서부터는 포기다.


이 당시 결제 문제로 서버 점검중이었는데 풀리고 나니까 설치 잘 된다. 아무래도 뭔가 문제가 있는 상황에서는 업데이트나 신규 설치를 막는 듯 하다. 물론 왜 막았는지 왜 안 되는지 바로바로 안 알려줘서 그렇지. 게임 서버 점검은 항상 급박하게 이뤄지고 그런 상황에서 유저에게 일일이 친절하게 대응하기가 쉽지 않다.

인터넷에 찾아보면 관리자 권한으로 실행하라는 둥 별 얘기 많은데, 혹시 문제가 있어서 점검중이면 끝날 때까지 기다려보자.
Read More

나이먹기 - 전래놀이 주제에 깊은 게임성

나이먹기는 내 평생 겪은 전래놀이 아니 그냥 게임 중에서도 가장 재미있던 놀이이다. 아주 어렸을 때부터 시작해서 중3때까지 했던 것 같다. 고등학교 올라가고 나서는 점심시간에 자느라 바빴지, 놀 생각을 못했으니까.

찾아보니 역시나 전래놀이답게 룰이 조금씩 다른데, 아무리 찾아봐도 내가 했던 룰을 찾을 수 없다. 다른 룰을 읽어보니 내가 했던 그 게임이 아니고 완전히 다른 게임을 얘기하고 있다. 그래서 내가 겪었던 로컬 룰을 소개한다.



필수요소


인원수는 최소 6명은 되야 재미있다. 많으면 12명정도 까지 좋고 그 이상은 번잡하다.

팀 게임으로서 두 편으로 나눠야 한다. 인원수가 달라도 상관은 없다.

두 팀은 각각 본진을 가지고 있다. 본진은 전봇대나 나무 기둥과 같은 봉 형태여야 한다. 너무 얇으면 안 되고 역시 전봇대가 적당하다. 두 본진과의 거리도 전봇대 거리 정도가 적당하다.

게임 시간은 보통 30분에서 1시간. 보통 200살 넘기 시작하면 그만 둔다.


기본 룰

(빨간색으로 표시된 것이 이 로컬룰의 특이한 점이다.)

모든 사람은 자신의 나이를 가진다. 나이는 절대 서로 같을 수 없다.

사람끼리 접촉(태그)이 일어나면 그 즉시 서로 나이를 따져본다. 비교해서 나이가 많은 쪽 팀이 30살을 먹는다. 나이를 먹은 팀은 팀내 가장 연소자에게 그 나이를 준다.

만약 나이를 먹어서 두 사람 이상이 같은 나이가 되면 반드시 서로 가위바위보를 해서 이긴 사람이 10살 더 먹는다. 여러 명이 같은 나이면 다 같이 가위바위보를 해서 진 사람 한 명은 자기 나이 그대로이고 나머지는 10살씩 먹고 또 가위바위보를 한다.

벌어들인 나이는 한꺼번에 먹을 수 없다. 내가 0살인데 상대가 10살이 있다면 반드시 10살 먹고 상대랑 가위바위보를 해야 한다. 만약 30살을 벌었으면 나(혹은 우리팀 꼴찌)에게는 3번의 기회가 있는 것이고 상대는 한 번만 지면 나보다 나이가 낮아지게 된다.

이렇게 나이가 새로 정해지면 반드시 본진을 한 번 터치하고 나가야 한다. 그렇지 않으면 무조건 0살이다. 즉 가위바위보가 끝날 때마다 본진을 찍고 시작한다. 기습적으로 엉뚱한 곳에서 숨어있을 수 없다.

같은 팀끼리 손을 잡고 있으면(아무 접촉이나 상관없다) 나이를 합칠 수 있다. 예를 들어 20살과 30실이 손을 붙잡고 있으면 40살을 이길 수 있다. 3명이 합쳐도 되고 팀 전체가 합쳐도 상관없다.

본진은 무한대의 나이를 제공한다. 즉 본진과 연결된 사람(들)은 무적이다. 그러나 본진을 직접 태그하면 태그한 팀이 50살을 먹는다. 이런 점은 야구와 비슷하다 홈 베이스를 밟은 상태에서 상대를 태그하면 아웃이지만 상대가 나를 피해서 홈 베이스를 밟으면 1점인 것이다.

게임이 끝날 때 나이를 많이 먹은 팀이 이긴다. 게임을 끝내는 시점은 자유다.


튜토리얼


처음에는 전부 다 0살이다. 다같이 모여서 가위바위보를 해서 0살이 누구인지 정한다. 그 다음 10살, 20살 등 모든 나이를 정한다. 

0살은 아무 데도 못 가고 무조건 본진 수비를 해야 한다. 0살의 비애 ㅜㅜ

 최고령자는 자기 마음대로 돌아다닐 수 있는데 달리기에 자신 없으면 2인 1조 공격을 조심해야 한다. 주로 상대 본진 뒤쪽을 노리는 공격을 한다. 달리기가 빠르면 0살이라도 도망다니면서 어그로를 끌 수도 있다. 꼭 달리기 자신있는 형들이 자기 나이랑 상관없이 시작하자마자 뛰쳐나가고 본다 ㅋㅋ

여러 명이 본진으로부터 손을 연결하면 리치가 매우 긴 무적 공격을 할 수 있다. 대신 본진 수비가 허술해진다. 기습적으로 둘 셋이 손을 잡고 본진을 노리는 상대에게 달려들면 효과적이다.

가끔 경찰과 도둑 게임으로 착각하고 한참 멀리 나가서 안 돌아오는 경우도 있다. 나이를 먹었는데도 안 보이면 일단 빼고 나이를 먹다가 돌아오면 겹치는 나이만 가위바위보를 한다. 멀리 나갔던 우리 편이 승전보를 들고 올 때 엄청 반갑다.

수비할 때는 360도를 다 봐야 하므로 정신이 하나도 없는데, 그게 또 재미지다.

서로 나이 계산이 안 되거나 서로 상대방이 몇 살인지 모른다거나 할 수도 있다. 나이를 반드시 알려줄 의무는 없고, 태그가 일어났을 때만 어느 쪽이 이겼는지 따져보면 된다. 이기는 나이인 데도 지는 척하고 도망가서 상대를 잡기도 한다. 상대를 쫒아가는데 상대가 갑자기 뒤를 돌게 되면 내가 설마 나이가 적던가 싶어서 되려 도망가기도 한다ㅋㅋ


왜 이 게임이 재미있는가


이 게임의 핵심 중의 핵심은 나이의 우세와 열세에 주어지는 보상이 확실하다는 것이다. 아무리 나이 어린 꼬맹이라도 가위바위보만 잘 하면 연장자가 되어 마음대로 돌아다닐 수 있는 힘을 만끽할 수 있다. 반대로 열세에 몰리면 본진만 붙잡고 살아야 한다. 나이가 많을 때 상대를 압도할 수 있는 이 느낌과 만족감이 중독의 핵심이다.

그러면서도 역전이 잘 나온다. 본진을 붙잡으면 무적이기 때문에 기습적으로 상대를 태그할 수도 있고, 합체나 다른 방법으로 불리한 상황을 뒤집을 수 있다. 가위바위보 실력(?)으로 역전도 가능하다. 완전히 불리해져서 본진 수비만 하더라도 상대를 이길 가능성이 없는 것은 아니기 때문에 얼마든지 재미있게 즐길 수 있다.

절대적인 팀 게임이다. 팀에서 벌어도 벌어들인 나이는 무조건 연소자에게 돌아간다. 캐리하는 기분 / 버스타는 기분을 느낄 수 있다. 집만 지키고 있는다고 해서 나중에 나이를 먹을 희망이 없는 게 아니다. 보상은 팀원 전체가 누린다.

0살에서부터 키워가는 육성의 재미가 있다.

처음에 0살에서 시작했어도 나이 10살만 딱 먹고 가위바위보만 다 이기면 가능성은 희박하지만 최고령자가 될 수 있다. 반대로 아무리 나이를 벌어도 가위바위보에서 지면 말짱 꽝이다. 실력 요소와 운 요소가 적절히 잘 결합되어 있다.

전략 요소가 충분하다. 상대 본진 방향의 얼굴쪽, 반대방향의 등쪽 모두 공간적으로 중요하다. 순간적인 합체를 통해 열세를 극복할 수 있고, 어그로를 끌고 돌아다니는 상대방을 구석으로 몰아서 잡을 수도 있다. 나이가 많다고 죄다 공격하러 나왔다면 본진의 빈틈을 노릴 수도 있다.

피지컬로 승부를 걸 수도 있다. 달리기가 빠른 사람은 나이와 상관없이 유리하니 나이의 상성을 뒤집는 재미가 있다. 불리한 상황을 실력으로 극복할 수 있는 것이다.

운동량이 많다. 뛰는 것은 그 자체로도 재미있다.


결론


정리하면서 생각해보니까 재미있을 수 밖에 없는 게임 요소를 모두 가지고 있다. 워낙 게임 요소가 탄탄해서 중독성이 있을 정도이다. 어릴 때 하던 변변찮은 놀이라고 생각하기에는 상당한 게임성을 가지고 있다. 가만 보니 롤이랑 많이 비슷한 것 같다.

이번 나이먹기를 시작으로 전래 놀이를 하나씩 기록해보고 싶다.

Read More

영빈혜선 결혼 축가






2020년 9월 5일 결혼식 축가

어쩌다 보니 결혼식이 계속 미뤄져서 너무 정성스럽게 만들게 됐다..
양가 모두 기독교 집안에 주례를 목사님이 하게 되므로 거기에 맞춰 가사를 썼음.
모티브가 된 곡은 CCM은 예수안에서, 누구나 삶의 시작은 작구나(한웅재)
그리고 김동률의 출발, 데파페페 Start.

왠지 자꾸 가사가 어둡게 나와서 곡이라도 최대한 밝은 분위기로 가려고 노력했다. 가사도 사실 더 어둡고 끈적끈적했었는데 아무래도 결혼식 분위기를 생각해서 힘찬 내용, 그리고 교회 다니는 어르신들이 좋아할 수 있는 바람직하고 성경적인 가사들로 채웠다.

가장 먼저 만들어진 가사는 '우린 보이지 않는 길을 믿음으로 가네', 그리고 '사랑 하나로 우리 그 먼 길을 갈 수 있을까' 이다. 결론적으로 사랑과 믿음만 있으면 얼마든지 갈 수 있다, 이런 내용. 그리고 후렴구는 어떤 믿음과 사랑인지를 구체화하는 내용들이다.

가장 마지막에 쓴 가사는 '주님의 사랑 뜻하신 계획 우리안에 이뤄지리라.' 이거는 그냥 뭐 공간은 남는데 가사는 더 없고 해서 상투적으로 쓰는 말들을 갖다 붙인 것.


음악 작업은 먼저 기타로 시작했다. 데파페페 Start와 같은 리듬으로 노래를 불러보면서 작곡을 끝내고, 편곡은 피아노부터 시작했다. 사실 이렇게 곡 전체를 피아노로 채울 생각은 없었는데 이러나 저러나 교회 음악은 역시 피아노가 가장 잘 어울린다. 피아노에서 베이스를 빼고 어택감을 살려서 소리가 뭉치지 않게  신경을 많이 썼다.

피아노 위에 퍼커션을 깐다. 그 다음 공간을 채우기 위해 아르페지에이터로 탱탱거리는 소리들을 넣었다. 메탈느낌의 짦은 벨이랑 마림바랑 신스콤프가 섞인 건데 이런 도움 없으면 인제 노래도 못 만든다. 어택을 짧게 해서 퍼커션을 보조하는 느낌으로 채우니 리듬도 살고 공간도 채우고 일석이조.

베이스도 마찬가지로 경쾌한 느낌으로  채웠는데 특이하게 베이스에 딜레이가 걸려 있다. 베이스도 아르페지에이터의 일부인 것처럼 넣고 싶었다.

가장 마지막에 들어간 것은 벨이다. 별로 복잡한 노트도 아닌데 곡과 어울리게 적절히 넣는 게 쉽지 않더라.

건반 좀 미스가 많은데 어떤 부분은 불협화음이 마음에 들기도 해서 그냥 놔둠. 불협화음이나 삑사리나는 소리들은 그 악기로 집중하게 만드는 효과가 있다. 어떤 악기가 작아서 잘 안 들리는데 무작정 음량을 키우기도 뭐할 때는 일부러 불협화음을 내는 것도 좋다. 플룻이 가끔 삑삑거리는 것도 다 그런 이유로 넣은 것.

마스터링을 잘 했어야 하는데 불륨이 작다. 하지만 원본 음질은 더 잘 보존한 셈... 으로 치자~ 퍼커션이 크게 들어가서 사실 컴프레싱이 쉽지 않은데, 그래도 퍼커션은 줄이기가 싫었다. 현장은 분명 모니터링이 잘 안 될 테니 노래를 잘 부르려면 박자라도 맞추기 쉽게 해야 한다.



최종적으로는 어쿠스틱 기타를 붙잡고 반주 위에 연주까지 곁들여서 노래하는 것이 원래 계획이었는데 그냥 번거롭고 해서 보컬만으로 공연.











 

Read More

파이썬에서 부울이 아닌 값에 대한 논리연산자

C먼저 확인해보면

C언어에서는 bool 타입이 따로 존재하지 않는다. 0이면 false, 그렇지 않으면 true이다. 심지어는 float도 피연산자로 참여할 수 있다. 그래도 어쨌든 논리연산자의 결과값은 0 아니면 1이 되는 1byte 정수형을 리턴한다.

예를 들어

int a = 10;

float b = 3.14;

printf("%d, %d\n", a && b, sizeof(a && b));

위의 코드에서 출력은 1, 1 이다.


파이썬의 and, or

그런데 파이썬은 조금 더 변태같다.

a = 10

b = 20

print(a and b, a or b)

결과는 20, 10이다. 어째써? and와 or는 다음과 같이 구현되어 있다.

a and b => if a is true, return b, else return a
a or b => if a is true, return a, else return b

and의 경우 a가 참이면 b를 리턴한다. 결과적으로 a and b가 참인지 거짓인지는 b에 달린 상황이다. 만약 a가 거짓이면 그냥 a를 리턴한다. a가 거짓인 줄 알고 리턴하니까 a and b도 거짓이 된다. 이 때 b는 체크하지 않는다.

or의 경우 a 가 참이면 그냥 a를 리턴한다. a가 참이니까 a or b 도 참이다. 이 때 b는 체크하지 않는다. 만약 a가 거짓이면 b를 리턴한다. a or b가 참인지 거짓인지는 b에 달렸다.

한 번 더 정리하자면 a and b 에서 a 가 거짓이면 b를 체크하지 않고 a를 리턴한다. a or b에서 a 가 참이면 b를 체크하지 않고 a를 리턴한다. a and b에서 a가 참이면 b를 리턴하고, a or b에서는 a가 거짓이면 b를 리턴한다.


C언어의 논리연산자는 정수형 입력을 받아서 정수형 출력을 한다. 파이썬의 논리연산자는 입력 타입이 정해져 있지 않다. 그래서 입력 타입이 부울이 아닌 경우 엉뚱한 결과가 발생한다.


bool이 아닌 값들의 참, 거짓

뭐 당연하겠지만 또 웃기는 것은 True의 타입은 <class 'bool'>인데 산술연산에 참여할 때는 정수 1처럼 여겨진다는 것이다.

print(True + True)

결과는 2이다.


또 한 가지 팁이라면 파이썬에서는 None을 거짓으로 취급한다. 반대로 리스트나 기타 오브젝트는 참으로 취급한다. 아래 예제에서

a = [1, 2, 3]

print(a or None)

print(None and True)

결과는 [1, 2, 3], 그리고 None이다.


그런데 비어있는 리스트, 문자열, 딕셔너리, 셋, 튜플 따위는 거짓으로 취급한다.

print( [] or dict() or set() or tuple() or '' or 10 )

결과는 10이다. 10 앞에 나열한 모든 것들은 전부 거짓으로 취급된다.



bool로 변환해야 True가 된다

여기 또 다른 함정이 있다. 예를 들어 10은 bool로 형변환하면 True가 되긴 하지만 그렇다고 1이 True인 것은 아니다.

a = 10

print(a is True)

print(a == True)

결과는 둘 다 False이다. a는 정수 10일 뿐, bool도 아니고 True도 아니다. 


if문을 쓰지 않고 a가 참인지 체크하려면 간단히 bool로 형변환해보면 된다.

print(bool(a))

결과는 당연히 True이다.


마지막으로 아래 식을 확인해보자.

print(type(a and True))

print(type(True and a))

결과는 <class 'bool'>, <class 'int'> 이다. and 연산의 리턴 타입을 보면서 파이썬의 함수는 리턴 타입이 정해져있지 않다는 사실이 새삼 현실로 다가온다.


and, or 응용하기

파이썬의 이러한 특성을 이용하면 변태같은 코딩이 가능하다.


if a < b : func() #이 문장은

a < b and func() #요렇게 바꿀 수 있다.


if not (a < b) : func()

a < b or func()


조건문 대신 and와 or를 쓸 수 있다. 영 and와 or가 어색하다면 and 대신 then, or 대신 otherwise로 읽어보면 조금 더 자연스럽다. 어셈블리어를 해봤으면 매우 자연스러울 수 있다, 점프처럼 생각하면 되니까.

여기에 보태서 None이 거짓으로 인식되는 점을 이용해 None 체크를 할 수 있다.


if a is not None: b = a

else: b = somethingDefault #이 두 문장은

b = a or somethingDefault #요 한 문장으로 바꿀 수 있다.


a를 b에 대입하는데 만약 None이면 미리 정해준 기본값을 대입하도록 한 것이다. 같은 원리로 비어있는 리스트를 체크할 수도 있다.


myList = []

x = min(myList or [-1])

x = min(myList) #에러 발생


만약 myList == [] 즉 비어있는 리스트라면 에러가 발생한다. 이걸 try 로 잡아서 어쩌구 해도 되지만 어차피 비어있을 경우 x에 기본값 -1을 넣을 작정이라면 위와 같이 or를 이용할 수 있다.


결론

당연히 논리연산자의 피연산자는 bool로 묵시적 형변환이 일어나는 줄 알았고, 당연히 출력은 무조건 bool인줄 알았는데 입력 타입을 그대로 살려서 출력한다니 좀 어이없긴 하다. 파이썬의 철학이 단 하나의 목적을 위한 단 하나의 아름다운 코드 아닌가? 이런 변태같은 부분은 파이썬의 기본 철학을 정면으로 부정하는 것이다. 통일성과 간편함을 동시에 추구하는 것이 그렇게 쉬운 일은 아닌 셈이다.

파이썬의 이런 점이 마음에 들지 않는다, 혹은 헷갈려서 공부 못하겠다 싶으면 정수나 기타 이상한 타입은 일절 논리 연산에 참여시키지 말아야 한다. 반드시 bool(a) 또는 a != 0 과 같은 코드를 써서 명시적으로 bool로 변환해주는 것이 좋다. C를 제외한 대부분의 언어, 그러니까 Java와 C#에서는 당연히 이렇게 한다. 논리연산에는 bool만 참여할 수 있기 때문에 반드시 명시적 형변환이 필요하다. 차라리 이게 나은 것 같은데.

Read More

프레젠테이션 샘플 : 전자쓰레기





예전에 누구 자료 만들어준다고 작업한 게 있다. 전자쓰레기를 주제로 한 발표인데, 내 프레젠테이션 스타일이 고스란이 녹아있어서 한 번 소개하고자 올려본다.

업무용, 혹은 제안서 발표용 프레젠테이션은 절대 이렇게 할 수 없다. 그런 데서는 ppt가 문서의 역할을 대신하기 때문이고, 그 쪽 세계에서 통용되는 양식이란 따로 있기 때문이다. 그러나 단순히 프레젠테이션 하나만을 생각한다면 아래 소개된 점들을 활용하면 좋다.

1. 획일적인 레이아웃 타파
 - 매 페이지 넘길 때마다 똑같은 장면이 나와서는 안 된다. 절대 지루하지 않도록 다양한 레이아웃을 활용한다.
 - 제목이 상단에 위치할 필요가 없다. 중간이나 하단에 있어도 된다. 제목이 없어도 된다.
- 화면을 꽉 채우는 그림
- 분위기에 따라 배경 색을 변화

2. 목차나 중간 제목이 없음
 - 목차는 앞으로 어떤 내용이 있을 것인지 전체를 보여주는 역할을 한다. 그런데 미리 알려주고 시작하는 건 재미를 반감시킨다. 흥미를 끌어올리려면 목차나 중간 제목을 없애는 선택도 좋다.

3. 제목보다 더 큰 메시지
 - 슬라이드 한 장의 내용을 요약하는 메시지를 마지막에 넣어서 강조시키면 뭔가 이해가 잘 되는 기분을 느끼게 해줄 수 있고 집중을 얻어내기 쉽다.
 - 이것도 너무 계속 쓰면 뻔한 느낌이 나니까 적당히 사용한다.

4. 시작하는 방법
 - 질문으로 시작한다.
 - 가장 쉽고 재미있는 것으로 시작한다.
 - 주제와 가장 근접한 질문일 수록 좋다.
 - 슬라이드를 다 마치고 사람들에게 던질 질문이 무엇인지 생각해본다, 그 중에서 시작할 때도 던질 수 있는 질문을 찾아서 처음과 끝 두 번 똑같은 화두를 던진다.
 - 밸런스 질문을 활용한다. (군대 두번 가고 10억 혹은 한 번도 안 가고 1000만원 납부 같은)

5. 서술 트릭
 - 숲에서부터 나무로 향하는 전형적인 탑다운 방식 외에도 다양한 서술의 방법이 있다.
 - 여기서는 전자쓰레기를 먼저 말하지 않고 익히 알고 있는 사실들을 나열하면서 본래 주제(전자 쓰레기)로 접근하는 방법을 썼다.
 - 슬라이드에서 제목이 나중에 올라온다.
 - 트릭이 너무 심하면 불편하고 이해하기 어려우니 적당히 쓴다.

6. 애니메이션
 - 모든 요소에는 사소한 애니메이션이라도 있어야 한다.
 - 애니메이션을 쓴다고 화면의 중앙을 너무 오래 비워놓으면 안 된다.

7. 위치 활용
 - 화면의 중앙이 제일 중요하다.
 - 극적인 효과를 위해 아래쪽이나 구석을 활용한다.

8. 동영상 편집을 한다면
 - Youtube 에 올릴 동영상이라고 생각하면 기존 관습적인 ppt 스타일에서 벗어나 현재 유행하고 있는 기법을 받아들이기 쉬운 마음가짐이 된다.

9. 사람이 먼저다
 - 프레젠테이션은 자료가 아니라 사람이 하는 것이다.
 - 스피치를 먼저 구상하고 자료가 뒷받침해야 한다.
 - 전체 내용을 몇 개의 문장으로 요악하면 그것이 전체 발표의 구성/흐름이 된다.
 (우리는 살면서 스마트폰 등 많은 전자 제품을 사고 버립니다. 그 버려진 전자제품들을 전자 쓰레기라고 하는데 지구 환경에 큰 문제입니다. 그러나 전자쓰레기를 재활용하면 좋은 자원이 될 수도 있습니다.)

10. 내용이 더 먼저다.
 - 내용이 충실하고 그 내용을 발표자가 잘 이해하고 있으면 자신감이 생겨서 자료고 뭐고 상관없이 알아서 잘 풀린다.
 - 사람들은 결국 내용이 좋아야 만족한다.

11. 안 듣는 척 다 듣는다.
 - 딴 짓 하는 사람들이 강의평가는 가장 냉정하다.
 - 좋은 푸줏간 주인은 좋은 쇠고기를 팔 뿐이다. 보든 안 보든 그저 열심히 해야 한다.
 - 어째 ppt 자료랑 상관 없는 내용인데 ㅋㅋㅋ


Read More
Powered by Blogger.