기획자 본인만큼 잡다한 블로그

greentec.egloos.com

포토로그


통계 위젯 (블랙)

1726
184
83810

flag counter

Flag Counter


로그라이크 게임 제작 - 스탯에 대하여 (2), 명중과 회피 게임기획

명중은 공격이 상대방을 맞출 확률에 관계하는 수치다. 이 수치가 높으면 안정적으로 적을 공격할 수 있지만 낮으면 손도 못 대고 사망하는 일이 발생하기도 한다. 스탯을 올린 결과가 시각적으로 즉시 드러나기 때문에 성장을 체감시켜주기에 좋은 수치이기도 하다.


명중률에 관한 가장 유명한 밈 중 하나. 이렇게 총을 가까이 대도 명중률이 65% (XCOM)


이 글에서는 디아블로2, 디아블로3, 디아블로3 확장팩을 거치며 명중률에 영향을 주는 스탯이 변화해 온 과정과 개인적인 경험을 써보기로 한다.
먼저 디아블로2에는 명중률에 영향을 주는 스탯으로 공격 등급(Attack Rating)이 있었다. 이 수치는 민첩(Dexterity) 스탯으로 올릴 수 있었고, 매직 아이템에 붙은 옵션으로 직접 올릴 수도 있었다. 디아블로2의 공격 등급은 다음과 같은 공식으로 공격의 명중률을 결정했다.[1]

Chance To Hit = 200% * {AR / (AR + DR)} * {Alvl / (Alvl + Dlvl)}

Abbreviations:
AR = Attacker's Attack Rating
DR = Defender's Defense rating
Alvl = Attacker's level
Dlvl = Defender's level

공격 등급의 문제는 직관적이지 않다는 것이었다. 명중률에 영향을 주는 수치는 내 공격 등급, 상대의 방어 등급(Defense Rating), 내 레벨, 상대의 레벨의 4개였기 때문에 공격 등급을 1 올렸을 때 명중률이 얼마나 오를지 가늠하기가 힘들었다. 다른 스탯과 마찬가지로 많이 올리면 좋은 게 아닌가? 라고 생각할 수도 있지만 문제는 다른 스탯과 공격 등급을 비교해서 어느 것을 고를지 선택할 때 발생했다. 플레이어가 공격 등급 스탯의 상대적 가치를 판단하기 힘들고 명중률은 일정 확률 이상 올라가면 상승이 잘 체감되지 않기 때문에 공격 등급은 다른 스탯과 비교해서 우선순위가 낮았다.
디아블로3 오리지널에서는 공격 등급이 삭제되고 공격 명중률은 100%가 되었다. 대신에 민첩 스탯을 올리면 회피 확률 수치가 올라가는 것으로 명중 관련 수치를 일원화했다. 민첩 스탯이 올라감에 따라 회피율은 계단식으로 올라갔다. 당연히 민첩 스탯이 높아질수록 회피율이 올라가는 속도는 낮아졌다. 대략 아래와 같은 공식이었다.[2]

if (Dexterity <= 100)
 DodgeChance = Dexterity * 0.001;
else if (Dexterity <= 500)
 DodgeChance = (Dexterity - 100) * 0.00025 + 0.1;
else if (Dexterity <= 1000)
 DodgeChance = (Dexterity - 500) * 0.0002 + 0.2;
else if (Dexterity <= 8000)
 DodgeChance = (Dexterity - 1000) * 0.0001 + 0.3;
else
 DodgeChance = 1;

여기서 문제는 민첩 스탯이 8000을 넘어갈 때 회피율(DodgeChance)이 1, 즉 100%가 되는 것이었다. 그리고 민첩이 주는 회피 외에 다른 스킬이나 아이템이 주는 회피 보너스도 존재했기 때문에 실제로는 민첩이 8000이 되지 않아도 회피율이 매우 높아지는 결과를 낳았다. 하지만 이렇게 회피율이 높아도 몬스터의 대미지 자체가 매우 높았기 때문에 60레벨 캐릭터라도 몬스터의 공격 한두 방에 사망할 수도 있었다. 따라서 민첩을 높일 때 대미지도 같이 올라가는 직업인 수도사와 악마사냥꾼은 회피 세팅을 맞추기 위해 특정 아이템 세팅과 스킬 세팅만 유지해야 했고 게임이 단조로워졌다. 특히 수도사는 근접 공격 캐릭터였기 때문에 회피 세팅이 되어있지 않으면 살아남을 수 없었다.
이 문제를 수정하기 위해 디아블로3의 확장팩인 영혼의 수확자(Reaper of Soul)에서는 전반적인 밸런스 조정과 함께 민첩의 회피 보너스를 삭제하고 민첩 1 당 방어도(Armor)를 1 올려주는 것으로 바뀌었다.[3] 힘(Strength) 스탯 1도 방어도 1을 올리고 있었기 때문에 이제 힘과 민첩 둘 중 어떤 것을 올리건 방어도가 올라가게 된 것이다. 참고로 방어도는 대미지를 분수함수 공식으로 일정 수치만큼 깎아준다. (공격과 방어도에 대해서는 다음 글에서 다룰 예정이다) 회피 보너스는 스킬 등 다른 곳에서만 얻을 수 있게 되었다. 패치 결과 계속 회피하다가 한두 번의 공격에 사망하던 예전의 패턴에서 여러 번의 공격을 받으며 캐릭터가 언제쯤 죽을지 알 수 있도록 바뀌어서 좀 더 예측 가능한 게임이 되었다.
개인 경험에서 나온 다른 사례도 있다. 내가 신입 기획자이던 시절, 서비스하고 있던 게임에서 이벤트 보스를 추가하게 되었다. 당시 유저들의 최고 레벨은 40이었고 이벤트 보스의 레벨은 45로 들어갔다. 그런데 문제는 레벨 차이에 따라 명중률에 차등이 주어지는 것이었다. 정확히 기억은 나지 않지만 대략 아래와 같은 표였다.

레벨 차이명중률 디버프
1-20%
2-40%
3-60%
4-80%
5+-100%


이 공식은 내가 신입으로 오기 전 작업하던 다른 기획자가 만들고 나간 것이었는데, 이 공식대로라면 유저들은 이벤트 보스를 열심히 때리기만 하고 화면 가득한 MISS 메시지를 보며 실제로 보스를 잡을 수는 없었다.


"이상해씨 등 긁는 데 천 원"이라던 어떤 뽑기방의 대형 이상해씨처럼 보스는 눈에 보이지만 잡을 수 없는 존재가 되었다.



당연히 이 공식을 고치자고 했지만 앞 사람이 남기고 간 것이어서 안된다는 답변만이 돌아올 뿐이었다. 결국 이 보스와 이 공식은 그대로 서비스에 들어갔고 며칠 만에 유저들의 원성에 긴급하게 다시 패치되어 5레벨 차이날 때의 명중률 디버프는 -100%에서 -30%로 바뀌었다.
무적인 느낌을 주는 것과 실제로 무적인 것은 다르다. D&D의 d20(20면체) 주사위로 명중 굴림을 판정할 때 1이 나오면 무조건 실패, 20이 나오면 무조건 성공하는 규칙이 있다. 아주 강한 캐릭터라도 5% 확률로 실패할 수 있고, 아주 약한 캐릭터라도 5% 확률로 무조건 공격을 성공시킬 수 있는 것이다. 그리스 신화의 아킬레스도 발목에 약점이 있었고 북유럽 신화에서 빛의 신 발두르는 연약한 겨우살이 가지에 죽었다. 무적보다는 무적이 아닌 것이 좀 더 현실같고, 게임적으로도 더 재미있다고 생각한다.


참고자료
[1] Diablo II Attack Rating: https://diablo.gamepedia.com/Attack_Rating_(Diablo_II)
[2] Diablo III 확장팩에서 회피율은 어떻게 계산되는가: https://gaming.stackexchange.com/questions/164575/how-is-dodge-chance-calculated-in-diablo-3-reaper-of-souls?noredirect=1
[3] Diablo III 2.1.0 PTR 패치 프리뷰: https://us.diablo3.com/en/blog/14244559/

<게임으로 익히는 코딩 알고리즘> - 김영기 지음 독서




탑코더, 리트코드, 해커랭크 등 수많은 알고리즘 공부&경쟁 사이트들이 있지만 알고리즘 문제 자체에 게임의 형식을 차용한 곳은 많지 않다. 메이저한 곳 중에서는 본서에서 다루고 있는 코딩 게임 사이트가 있다. 여기에 올라오는 퍼즐 중 사이트에서 직접 제공하는 것들은 모두 그래픽을 씌운 게임의 형태이다. 쉬운 퍼즐도 화려한 그래픽으로 무장하고 있기 때문에 처음 알고리즘을 공부하는 사람이라면 큰 노력없이 자신이 짠 코드에 그래픽이 덧붙여져서 작동하는 것을 감상할 수 있기 때문에 좀 더 흥미를 가질 수 있다.

나는 이 사이트에 예전부터 가입해 있었지만, 이 책을 받고 나서 다시 들어가보니 예전 기록은 13레벨에서 멈춰 있었다. 책에서 제공하는 예제를 풀고, 사이트가 아닌 유저들이 직접 만든 커뮤니티 퍼즐을 몇 개 더 풀고 나니 15레벨이 되었다. 사이트에서 제공하는 퍼즐은 Easy, Medium, Hard, Very Hard의 난이도로 구분되어 있다. 책에서는 이 중 Easy 3문제, Medium 8문제, Hard 2문제를 다룬다. 언어로는 python을 사용하고, 독자가 노력 없이 답만 베껴서 문제를 푸는 것을 막기 위해 답의 전문을 제공하지는 않는다. Easy와 Medium 문제들을 다룰 때 저자는 문제와 함께 이 문제를 풀기 위한 프로그래밍 기본 개념을 자세히 설명한다. 마치 프로그래밍 입문서라고 해도 손색이 없을 정도이고 책도 컬러이기 때문에 초심자에게 추천할만하다. 예전의 나처럼 기초가 부족한데 의지도 부족한 사람이라면 이 책을 집어들고 Easy 장의 코드를 하나하나 따라하면서 문제도 풀고 사이트의 레벨도 올릴 수 있겠다. 게이미피케이션(Gamification)은 언제나 보통 이상을 해내는 훌륭한 도구이다.

책의 내용에 비해 제목이 좀 아쉬운데 처럼 책에서 다루는 사이트를 직접 제목에 명시해도 좋았을 것 같지만, 독자를 한정하는 효과도 있었을 것 같다. codingame이 top coder만큼 유명한 사이트는 아니기 때문에 홍보에 큰 도움이 되지 않을 것이라고 생각했을지도 모르겠다.

<케라스 창시자에게 배우는 딥러닝> - 프랑소와 숄레 지음, 박해선 옮김 독서





현재 세계에서 가장 많이 쓰이는 딥러닝 프레임워크는 무엇일까? Jeff Hale 이라는 사람이 구글 검색 통계, 아마존 출간 부수, 논문, github repository 수 등 다양한 팩터를 종합해서 정리한 <Deep Learning Framework Power Scores 2018>에 따르면 1위는 텐서플로우, 2위는 케라스이다. 그런데 1위인 텐서플로우는 구글이라는 거대한 기업에서 만들었지만 2위인 케라스를 처음에 시작한 사람은 단 한 명, 프랑소와 숄레라는 사람이었다. 프랑소와 숄레는 케라스를 만든 뒤에 구글 브레인에 들어갔지만 케라스는 그와 상관없이 텐서플로우 팀에서 먼저 텐서플로우 1.x 버전의 차세대 상위 API로 사용하기로 결정되었고, 텐서플로우 2.0이 된 지금은 케라스의 문법을 텐서플로우 안에서 사용하는 tf.keras가 상위 API를 호출하는 표준으로 정리되었다. 사실상 1위와 2위는 하나로, 딥러닝 프레임워크는 어느 정도 통일이 된 셈이고 그 중심에는 케라스의 창시자인 프랑소와 숄레가 있는 것이다.

이 책은 그런 프랑소와 숄레가 쓴 책, <Deep Learning with Python>의 한글 번역본이다. 번역본 제목도 눈길을 끄는데 케라스는 텐서플로우만큼이나 유명하기 때문에 이 라이브러리의 창시자가 쓴 책이라고 하면 관심이 갈 수 밖에 없을 것이다. 나도 이 책을 봐야겠다고 생각만 하던 중에 길벗출판사의 독자 이벤트에 당첨이 되어서 책을 받아볼 수가 있었다. 476페이지의 두꺼운 책이었지만 받자마자 5일 만에 독파할만큼 집중해서 읽었다.

초기의 딥러닝 라이브러리들 - theano, Tensorflow 등은 일반 연구자가 접근하기 어려운 점들이 있었다. theano와 Tensorflow는 둘 다 그래프 구조를 사용했다. 그래프 구조가 문제가 되는 것은 선(先) 정의 후(後) 평가 구조이기 때문이다. 처음에 딥러닝 연산을 수행하는 기본 단위인 텐서가 어떻게 계산되는지에 대한 그래프 구조를 정의하고, 나중에 계산할 때 한번에 각 그래프의 노드에 있는 값들을 확인할 수 있는 구조이다. 즉 정의만 했을 때는 텐서에 어떤 값이 들어갈지 알 수가 없는 구조이다. 이 밖에도 라이브러리가 일반 연구자들이 보기에는 불편한 점이 많았고, 그래서 theano에는 lasagne, Tensorflow에도 여러 개의 추상화 라이브러리가 생겨서 사용자가 좀 더 쉽게 접근할 수 있도록 하는 시도들이 있었다. 그 중에서도 케라스가 독특했던 점은 theano의 추상화 라이브러리에서 멈추지 않고 tensorflow, CNTK 등 여러 개의 딥러닝 라이브러리에서 하나의 코드로 같은 동작을 하도록 하는 범용 추상화 라이브러리를 만들려고 했다는 점이었다.

이 대담한 도전은 멋지게 성공했다. theano는 개발이 중단되었지만 Tensorflow는 세계에서 가장 널리 쓰이는 딥러닝 라이브러리가 되었고 케라스는 Tensorflow의 추상화 표준이 되었다. Microsoft의 CNTK도 2.0에서 케라스를 지원하는 기능을 추가했다.

케라스의 장점은 짧고 간결하면서 이해하기 쉬운 코드이다. 이에 대해서는 내가 예전에 발표했던 <텐서플로우 첫걸음>의 6페이지를 보면 코드 길이의 차이를 직관적으로 확인할 수 있다.


왼쪽과 오른쪽은 같은 일을 하는 코드이다. Tensorflow 1.x 기준



저자인 프랑소와 숄레는 책에서 코드를 중심으로 설명하려고 노력했고, 필요한 경우가 아니면 수학을 쓰지 않으려 했다고 말한다. 프로그래머에게는 몇 줄의 코드가 복잡한 수식보다 이해하기 쉽기 때문이라고 그 이유를 설명하는데, 공감이 가는 말이었다. 프레임워크를 만들어본 사람답게 다양한 데이터와 논문, 연구자들과의 교류 내용을 곁들이지만, 방대한 내용에 대해서 깔끔하고 분명하게 설명하려고 노력한다. 마치 저자가 만든 케라스처럼 말이다.

이 책에서 또 한가지 주목할 점은 페이지의 하단을 빼곡하게 채우고 있는 역자의 역주이다. 역주에는 독자들이 생소하게 느낄 수 있고 저자는 그냥 지나친 코드에 대한 설명부터, 저자가 언급한 내용에 대한 실제 논문, 독자가 알아둬야 할 사항들 등이 꼼꼼하게 적혀 있다. 역주를 읽는 것만으로도 내용 이해가 훨씬 잘 되는 것을 느꼈고 반대로 역주가 없었다면 내용 이해가 쉽지 않았을 것 같다는 생각이 들었다. 또 번역도 억지로 한글 표기를 고집하지 않고 영어를 써야 할 곳에는 영어를 적절하게 사용하고 뜻이 중복될 수 있는 단어에 대해서도 분명하게 설명하고 넘어가는 등 최근 본 번역서 중 제일 좋은 책이었다. 역자는 개인 블로그를 운영하면서 오탈자와 코드도 계속 업데이트하기 때문에 책을 읽은 사람은 방문해보면 좋을 것 같다.

여러 가지로 최근 읽은 딥러닝 책 중 가장 만족도가 높은 책이었다. 3, 4년 전과는 다르게 딥러닝 책이 시장에 정말 많이 나오고 있는데, 이 분야를 처음 공부하는 사람이라면 기초적인 내용을 공부한 다음에는 이 책으로 중급 정도의 실력을 쌓아보는 것이 좋을 것 같다.


트위터 언팔로워 트래커 프로그래밍




트위터에는 사용자에게 꼭 필요하지만 빠져 있는 기능이 있다. 바로 나를 언팔로우한 사람이 누군지 알려주는 기능이다. 언팔로우란 팔로우를 하다가 팔로우를 끊는 것을 말한다. 페이스북으로 치면 unfriend, 싸이월드로 치면 일촌끊기가 되겠다. 일단 관계가 있어야 관계를 끊을 수 있다. 먼 옛날 싸이월드와 달리 인스타그램이나 트위터는 팔로워 수가 이 계정의 유명세에 대한 하나의 지표가 된다. 유명세는 권력, 힘이 된다.

트위터를 처음 시작한 초반에는 팔로우를 해주는 사람들이 그냥 고마웠다. 그래서 내 관심주제랑 어느 정도 겹치면 자연스럽게 맞팔로우를 하기도 했다. 그런데 그러고나서 매우 짧은 시간 후에 나에 대한 팔로우를 끊는 것을 몇 번 목격했다. 이런 것을 반복하면 이 사람의 팔로잉 수는 줄고 팔로워 수만 늘어서 자연스럽게 힘을 획득하게 된다. 양심이라는 아주 싼 가격에 말이다.
(관련글 링크 : 인스타그램 맞팔 후 언팔)

뭐, 물론 언팔을 하는 쪽에서도 변명할 수는 있겠지만 그런 것보다 중요한 것은 트위터의 팔로워 수가 100 명을 넘어가기 시작하면 눈으로 보고 누가 언팔로우를 했는지 알아내기는 쉽지 않다는 사실이다. 인터넷에 검색해보니 언팔매니저 같은 것들이 있는데, 여기까지 들어온 분들이라면 직접 코드를 사용하는 방법에 관심이 있을 것 같다. tweepy 를 사용하면 매일 내 팔로워를 긁어온 다음에 어제의 날짜와 비교해서 누가 팔로우를 끊었는지 알아낼 수 있다. 언팔매니저는 맞팔을 안해준 사람들을 찾는 것이 목적이라면 내가 만드는 코드는 언팔로워만 찾는 것이 목적이다.

그럼 간단한 코드를 보자. 짧은 코드이니만큼 전체를 리뷰하며 한 줄 한 줄 설명하도록 하겠다.
이 글은 python 3.6.2, tweepy 3.6.0 을 기준으로 한다.

  1. 트위터 API 접속

  2. 트위터는 API 를 제공하기 때문에 접속해서 나의 팔로워 수, 팔로워 정보, 트윗 등을 얻어올 수 있고 직접 트윗을 올릴 수도 있다. 나도 이것으로 2개의 트위터 봇을 라즈베리파이에서 돌리고 있다. 라즈베리파이는 설정 과정이 너무 고통스러웠기 때문에 빨리 백업을 해놔서 보드가 죽어도 다른 곳에서 쓸 수 있게 해야 하는데.. 아직 귀찮아서 못하고 있다. 어쨌든 이 서두 부분은 Twitter API 에 접속하기 위해서 꼭 필요하다.


    import tweepy
    from credentials import *

    # tweeter initialize!!
    auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
    auth.set_access_token(access_token, access_token_secret)
    api = tweepy.API(auth, wait_on_rate_limit=True, wait_on_rate_limit_notify=True, compression=True)



    일단 tweepy 는 트위터 API 를 간단한 명령어로 편하게 쓸 수 있게 해주는 python 라이브러리다. 윈도우 환경에서는 보통 Anaconda 를 쓴다면 pip install tweepy 같은 간단한 명령어를 cmd 창에서 입력해서 설치할 수 있다.

    credentials 는 이 코드에서 다루는 파일과 같은 위치에 저장된 credentials.py 파일을 말한다. developer.twitter.com 에 접속해서 App 을 사용하도록 설정하면 다음과 같은 Keys and tokens 를 받을 수 있다. 이 값들을 credentials.py 에 저장해놓고, 불러와서 twitter API 접속에 사용하는 것이다.




    credentials.py 에는 다음처럼 값을 저장해준다.

    consumer_key = 'Zzzzzzzzzzzzzzzzzzzzzzzz'
    consumer_secret = 'Mmmmmmmmmmmmmmmmmmmmmmmmmm'
    access_token = '11111111111111111-222222222222222'
    access_token_secret = 'eeeeeeeeeeeeeeeeeeeeee'




  3. 어제, 오늘의 팔로워 정보 얻어오기

  4. 다음 부분은 어제의 팔로워 정보를 로드하는 부분이다. 그런데 이 글을 따라오는 분들이라면 어제의 팔로워 저장 파일은 당연히 없을 것이기 때문에,지금은 써놓기만 하고 주석처리 후 뒤에서 팔로워 정보를 한번 저장한 후에 로드하면 된다.

    # load file
    original_ids = []
    with open(pi_path + 'follower.txt', 'r') as f:
    for line in f.readlines():
    if line != '':
    original_ids.append(line.strip())




    오늘의 팔로워 정보는 직접 tweepy 에서 얻어온다. 여기서 나오는 Cursor 가 궁금한 분들은 이곳에서 간단한 글을 읽어보셔도 좋겠다. 요약하면 Cursor 는 기존의 page 방법과 다르게 boilerplate code (어떤 업무를 위해 반복적으로 작성해야 하는 코드) 를 없애고 간단하게 같은 작업을 할 수 있는 표현법이다.

    이 코드는 이 gist 에서 가져왔다.

    print("Loading followers..")
    followers = []
    for follower in tweepy.Cursor(api.followers).items():
    followers.append(follower)

    print("Found %s followers, finding friends.." % len(followers))
    friends = []
    for friend in tweepy.Cursor(api.friends).items():
    friends.append(friend)

    print('Found %s friends..' % len(friends))


    # creating dictionaries based on id's is handy too
    friend_dict = {}
    for friend in friends:
    friend_dict[str(friend.id)] = friend

    follower_dict = {}
    for follower in followers:
    follower_dict[str(follower.id)] = follower




  5. 어제, 오늘의 정보 비교해서 언팔로워 알아내기

  6. 이 부분도 2에서 어제의 팔로워 정보가 없어서 original_ids 가 [] 상태인 분들은 모든 사람이 unfollowers 로 뜰 것이다. 그렇지 않다면 정확한 unfollowers 와 unfollowers_and_still_friends 가 뜰 것이다. unfollowers 는 어제와 비교해서 오늘 follower 가 아닌 사람(언팔로우한 사람), unfollowers_and_still_friends 는 그 중에 아직 내가 팔로우하고 있는 사람이다. 내가 관심있는 건 뒷부분이다. 내가 팔로우 중이지만 나를 언팔로우한 경우.

    unfollowers = []
    unfollowers_and_still_friend = []
    for id_ in original_ids:
    if id_ not in follower_dict:
    unfollowers.append(id_)
    if id_ in friend_dict:
    unfollowers_and_still_friend.append(id_)

    print('unfollwers:', len(unfollowers))
    print('unfollowers_and_still_friend:', len(unfollowers_and_still_friend))




  7. 변경된 팔로워 저장

  8. 오늘의 팔로워 정보만 저장한다. 좀 더 자세히 한다면 이 사람이 언제 팔로워가 됐는지 하는 것들도 저장 가능하겠지만, 나는 라즈베리파이에서 이 스크립트를 돌릴 것이기 때문에 가능한 최소한의 저장 용량만 쓰고 싶었다. 오늘의 팔로워 목록만 있으면 내 목적은 달성된다.

    이제 2에서 어제의 팔로워 정보가 없어서 로드를 못하셨던 분들은 로드를 할 수 있다. 다만 팔로워가 많을 경우 tweepy 의 기능을 여러 번 사용하면 limit 가 걸릴 수 있다는 점에 주의하자. 이럴 때는 일정 시간 동안 기다렸다가 다시 시도하면 된다.

    # save file
    with open(pi_path + 'follower.txt', 'w') as f:
    for id_ in follower_dict:
    f.write(str(id_) + '\n')




  9. 메일로 보내서 자동화

  10. 이제 스크립트는 완성되었다. 이 스크립트를 매일 돌려도 된다. 하지만 좀 더 자동화하면 내 삶이 편해질 것 같다. 라즈베리파이에 넣어서 일정 시간마다 스크립트가 실행되도록 하고, 해당 내용을 메일로 받아보고 싶었다.

    나는 gmail 을 사용하는데 yagmail 이라는 라이브러리를 설치하면 좀 편하게 메일을 보낼 수 있다. 사용 방법은 아까처럼 cmd 창에서 pip install yagmail 을 입력하면 설치된다. 원래는 smtplib 라이브러리를 쓰려고 했는데 gmail 접속이 안돼서 이걸로 바꿨다. 그런데 스크립트로 메일이 안갈 때의 해결책이 지메일에서 '보안성이 낮은 앱의 접근을 허용' 옵션을 켜는 것이었기 때문에, 사실 smtplib 을 써도 되는 게 아니었나 생각한다.

    import smtplib
    from email.mime.text import MIMEText
    import datetime
    import yagmail

    if len(unfollowers) > 0:
    now = datetime.datetime.now()
    nowDate = now.strftime('%Y-%m-%d')

    mail_str = ''
    for id_ in unfollowers:
    if id_ in unfollowers_and_still_friend:
    mail_str += 'unfollower and still friend: ' + id_ + '\n'
    else:
    mail_str += 'just unfollower: ' + id_ + '\n'

    mail_str += '\n'
    mail_str += 'unfollower: ' + str(len(unfollowers)) + '\n'
    mail_str += 'unfollowers_and_still_friend: ' + str(len(unfollowers_and_still_friend)) + '\n'


    sub = '[트위터 Unfollower Tracker] ' + nowDate + ' unfollower 현황입니다.'

    yag = yagmail.SMTP("send_email_address", "password")
    yag.send("receive_email_address", subject=sub, contents=mail_str)





이것으로 자동화가 완성되었다. 이제 나를 팔로우하는 사람들에게 자신있게 맞팔로우를 할 수 있다...


* 후기

이 시스템을 만들고 며칠 동안 언팔로워가 없어서 제대로 테스트하지 못하다가, 드디어 오늘 언팔로워가 생겨서 메일을 받아볼 수 있었다. 결론은 잘 동작한다. 




게임 장르의 욕망이 사라질 때 글쓰기

모든 게임은 밤낮을 잊고 재밌게 플레이하다가도 어느 순간 그만두게 되는 시점이 온다. 게임 장르별로 플레이를 지속하게 만드는 욕망은 다르다. 오늘은 문득 길을 걷다 이것에 대해 글을 쓰고 싶어졌다. 욕망의 개인차가 있을 수 있기 때문에 주로 나의 경우를 예를 들어 설명해본다.





수집형 게임
- 카드, 캐릭터, 아이템 등 뭔가를 모아야 하는 게임들은 패키지가 아니라면 주기적으로 업데이트를 해서 수집 목록을 갱신하게 한다.
욕망 : 목표로 하는 단품 혹은 콜렉션을 얻고 싶다. 얻은 후 강화해서 쓸만하게 만들고 싶다.
사라질 때 : 원하는 것을 얻었다. 또는 원하는 것을 얻기까지의 과정이 너무 힘들어 보인다(과금 & 시간 많이 소요). 내 자산의 가치가 폭락했다(주로 업데이트로 인해).





전략형 게임
- LOL, 스타크래프트, 클래시 로얄 등 보통 PvP 를 상정한 게임이다. 게임은 비교적 공평하며 플레이어의 과금보다는 주로 실력으로 승부하게 된다.
욕망 : 게임을 좀 더 잘하고 싶다. 상대를 이기고 싶다. 높은 랭크에 올라가고 싶다.
사라질 때 : 시간 투입에 비해 게임 실력이 늘지 않는다. 여러 가지 정상적이지 않은 방법으로 높은 랭크를 획득했다(대리 게임 등).





샌드박스형 게임
- Don't starve, 마인크래프트, 롤러 코스터 타이쿤 등 주어진 세계의 룰 안에서 하고 싶은 것을 하며 노는 게임이다.
욕망 : 시간이나 게임 시스템의 압박 없이 자유롭게 플레이하고 싶다. 이 세계의 구성 요소를 조합해서 새로운 것을 만들고 싶다. 남들이 만든 창작물을 보고 더 좋은 것을 만들고 싶다. 나의 개입 없이도 잘 돌아가는 세계(system)를 만들고 싶다.
사라질 때 : 게임이 너무 어렵다. 내가 만들어 놓은 세계가 너무 잘 돌아가서 내가 할 게 없다(매우 드문 경우지만). 다른 사람들의 엄청난 창작물을 보고 좌절감을 느낀다.





스토리 게임
- 텔테일 게임즈의 게임들, 디트로이트 비컴 휴먼 등 인터랙티브 무비라 불리는 장르의 게임들이 주로 이에 해당된다. 위처, 레드 데드 리뎀션 같은 RPG 게임들도 메인 스토리를 보면 이쪽에 발을 걸치고 있다고도 할 수 있다.
욕망 : 스토리의 끝을 알고 싶다. 스토리의 과정을 하나하나 참여하며 진행하고 싶다.
사라질 때 : 스토리의 엔딩을 봤다. 스토리의 주요 반전을 스포일러 당했다. 스토리에 내가 미치는 영향이 미미하다(주로 텔테일. 주요 선택지에 따라 ㅇㅇㅇ는 기억할 것입니다 라는 메시지만 나오고 결과에는 영향이 거의 없음).





퍼즐 게임
- 지뢰찾기, 카드게임부터 캔디 크러시 사가나 Hitman GO 등 주어진 퍼즐을 해결하는 것이 메인 콘텐츠인 게임이다.
욕망 : 게임에 존재하는 퍼즐 혹은 임의의 퍼즐을 모두 풀고 싶다.
사라질 때 : 퍼즐이 너무 어렵다. walkthrough 등의 영상을 보고 퍼즐을 따라서 풀었지만 이해가 되지 않는다. 퍼즐 양이 많지만 베리에이션이 부족하고 다 비슷하게 보여서 퍼즐을 푸는 것이 노가다를 하는 느낌이라 풀고 싶지 않다.





오픈 월드 게임
- 게임 내 배경이 존으로 구분되지 않고 하나로 통합되어 있는 게임으로, 월드에 배치된 다양한 콘텐츠를 자유롭게 이동하며 즐길 수 있다.
욕망 : 게임에서 제공하는 콘텐츠를 모두 클리어하고 싶다. 게임에서 강제하는 순서가 아니라 내가 원하는 것부터 마음대로 즐기고 싶다.
사라질 때 : 게임에서 제공하는 콘텐츠를 모두 클리어했다. 모두 클리어하지 못할만큼 콘텐츠 양이 많은데, 콘텐츠들이 다 비슷해서 계속하고 싶은 마음이 들지 않는다. 스토리가 너무 유치하거나 말이 안돼서 몰입이 안된다(스토리가 있는 게임의 경우. 대부분 오픈월드 게임들은 스토리가 있는 것 같다).





1 2 3 4 5 6 7 8 9 10 다음

애드센스