발표자 소개
옥찬호 (ChrisOhk)
• Nexon Korea 게임 클라이언트 프로그래머
• Microsoft Developer Technologies MVP
• 페이스북 그룹 C++ Korea 대표
• IT 전문서 집필 및 번역 다수
• 게임샐러드로 코드 한 줄 없이 게임 만들기 (2013)
• 유니티 Shader와 Effect 제작 (2014)
• 2D 게임 프로그래밍 (2014)
• 러스트 핵심 노트 (2017)
• 모던 C++ 입문 (2017)
• C++ 최적화 (2019)
utilForever@gmail.com
utilForever
감사의 말
• 발표할수 있도록 도움을 주신 전이삭 님께 감사드립니다.
• RosettaStone 프로젝트를 함께 만들어 나가는 팀원분들께 감사드립니다.
• 김영중 님, 전승현 님, 김성현 님, 김형찬 님
박유한 님, 박준영 님, 김현수 님, 김성황 님
송태현 님, 이도운 님, 정연수 님, 조수하 님
• 그 외 프로젝트에 기여해주시는 모든 분들께 감사드립니다.
9.
하스스톤
• 블리자드 엔터테인먼트가개발한 디지털 카드 수집 게임
• 2013년 3월 페니 아케이드 엑스포에서 처음 발표
• 2014년 3월 13일 PC 버전부터 출시
• 현재까지 11개의 확장팩과 4개의 모험 모드가 추가됨
• 2018년 11월 발표 기준 전세계 이용자수 1억명 돌파
https://ko.wikipedia.org/wiki/하스스톤
그러던 도중 ‘알파고’와‘알파스타’를 보고 취해버린 저는
하스스톤도 저렇게 할 수 있지 않을까라는 생각을 하게 됩니다.
20.
목표
• 강화학습을 통해
•영리한 플레이를 할 수 있게 만들어 보자.
(하스스톤 프로게이머와 대적할 만한 수준)
• 승률이 높은 덱을 만들어 보자.
(1티어 덱보다 높은 승률 : >= 60%)
• 플레이어가 덱을 만들 때 어떤 카드가 없을 경우
대체 카드를 추천하는 기능을 제공해 보자.
https://hsreplay.net/
강화학습
• 아이가 첫걸음을떼는 과정
• 아이는 걷는 것을 배운 적이 없습니다.
• 아이는 스스로 이것저것 시도해 보다가 우연히 걷게 됩니다.
• 자신이 하는 행동과 걷게 된다는 보상 사이의 상관관계를 모르는 아이는 다시 넘어집니다.
• 시간이 지남에 따라 그 관계를 학습해서 잘 걷게 됩니다.
23.
강화학습
• 에이전트는 사전지식이 없는 상태에서 학습합니다.
• 에이전트는 자신이 놓인 환경에서 자신의 상태를 인식한 후 행동합니다.
• 환경은 에이전트에게 보상을 주고 다음 상태를 알려줍니다.
• 에이전트는 보상을 통해 어떤 행동이 좋은 행동인지 간접적으로 알게 됩니다.
24.
강화학습
• 에이전트는 사전지식이 없는 상태에서 학습합니다.
• 에이전트는 자신이 놓인 환경에서 자신의 상태를 인식한 후 행동합니다.
• 환경은 에이전트에게 보상을 주고 다음 상태를 알려줍니다. (오늘의 주제)
• 에이전트는 보상을 통해 어떤 행동이 좋은 행동인지 간접적으로 알게 됩니다.
25.
알파고
• 바둑판 위에모든 상황에 대한 정보가 있습니다.
• 두 선수가 돌을 하나씩 번갈아 놓으며 진행합니다.
https://shuuki4.wordpress.com/2016/03/11/alphago-alphago-pipeline-헤집기/
26.
알파스타
• 실시간 전략게임(RTS)이며 불완전한 정보가 주어집니다.
• 유닛의 시야 범위 안에서만 정보를 얻을 수 있으며,
정찰을 보내 탐색전을 펼치며 상대방의 상황을 추론해야 합니다.
• 실시간으로 넓은 전장에서 수백개의 유닛과 건물을 제어하며
다양한 조합을 통해 전략을 짜야 합니다.
https://www.bloter.net/archives/329970
하스스톤
• 카드 수집게임(CCG)이며 불완전한 정보가 주어집니다.
• 내 덱에서 다음에 무슨 카드가 나올지 예측할 수 없습니다.
• 상대방이 현재 손에 무슨 카드를 들고 있는지 예측할 수 없습니다.
• 상대방 덱에서 다음에 무슨 카드가 나올지 예측할 수 없습니다.
• 알파고나 알파스타와 달리 자신의 정보조차 불완전합니다.
하스스톤 강화학습 연구현황
• Świechowski, Maciej, Tomasz Tajmajer, and Andrzej Janusz. “Improving Hearthstone AI
by Combining MCTS and Supervised Learning Algorithms.” 2018 IEEE Conference on
Computational Intelligence and Games (CIG). IEEE, 2018.
• Zhang, Shuyi, and Michael Buro. "Improving hearthstone AI by learning high-level rollout
policies and bucketing chance node events." 2017 IEEE Conference on
Computational Intelligence and Games (CIG). IEEE, 2017.
• Kachalsky, Ilya, Ilya Zakirzyanov, and Vladimir Ulyantsev. "Applying reinforcement learning
and supervised learning techniques to play Hearthstone." 2017 16th IEEE International
Conference on Machine Learning and Applications (ICMLA). IEEE, 2017.
33.
3. 하스스톤 게임개발
• 하스스톤을 개발하게 된 이유
• 하스스톤의 구조
• 카드 구현
• 카드 효과 구현
• 게임 구현
34.
게임을 활용해 강화학습을하려면
• 첫번째 방법 : 게임 회사에서 제공하는 API를 사용합니다.
35.
게임을 활용해 강화학습을하려면
• 첫번째 방법 : 게임 회사에서 제공하는 API를 사용합니다.
• 스타크래프트 2는 있는데, 하스스톤은 없습니다. (실패)
36.
게임을 활용해 강화학습을하려면
• 첫번째 방법 : 게임 회사에서 제공하는 API를 사용합니다.
• 스타크래프트 2는 있는데, 하스스톤은 없습니다. (실패)
• 두번째 방법 : 게임을 후킹(Hooking)해 알아낸 정보를 사용합니다.
38.
게임을 활용해 강화학습을하려면
• 첫번째 방법 : 게임 회사에서 제공하는 API를 사용합니다.
• 스타크래프트 2는 있는데, 하스스톤은 없습니다. (실패)
• 두번째 방법 : 게임을 후킹(Hooking)해 알아낸 정보를 사용합니다.
• 서비스 중인 게임에 영향을 주는 행위는 불법입니다. (실패)
39.
게임을 활용해 강화학습을하려면
• 첫번째 방법 : 게임 회사에서 제공하는 API를 사용합니다.
• 스타크래프트 2는 있는데, 하스스톤은 없습니다. (실패)
• 두번째 방법 : 게임을 후킹(Hooking)해 알아낸 정보를 사용합니다.
• 서비스 중인 게임에 영향을 주는 행위는 불법입니다. (실패)
• 세번째 방법 : 하스스톤을 직접 만들어 사용합니다.
• 아, 이 방법 만큼은 쓰고 싶지 않았는데…
• 근데 대안이 없습니다. 어쩌겠습니까, 맨 땅에 헤딩하는 수 밖에… ㅠㅠ;
카드 데이터 처리
•하스스톤에는 다양한 카드가 존재하며 데이터가 각각 다릅니다.
• 모든 카드는 마나 코스트를 갖습니다.
48.
카드 데이터 처리
•하스스톤에는 다양한 카드가 존재하며 데이터가 각각 다릅니다.
• 모든 카드는 마나 코스트를 갖습니다.
• 하수인 카드는 공격력과 체력을 갖습니다.
49.
카드 데이터 처리
•하스스톤에는 다양한 카드가 존재하며 데이터가 각각 다릅니다.
• 모든 카드는 마나 코스트를 갖습니다.
• 하수인 카드는 공격력과 체력을 갖습니다.
• 무기 카드는 공격력과 내구도를 갖습니다.
50.
카드 데이터 처리
•하스스톤에는 다양한 카드가 존재하며 데이터가 각각 다릅니다.
• 모든 카드는 마나 코스트를 갖습니다.
• 하수인 카드는 공격력과 체력을 갖습니다.
• 무기 카드는 공격력과 내구도를 갖습니다.
• 어떤 카드는 어빌리티를 갖습니다.
51.
카드 데이터 처리
•하스스톤에는 다양한 카드가 존재하며 데이터가 각각 다릅니다.
• 모든 카드는 마나 코스트를 갖습니다.
• 하수인 카드는 공격력과 체력을 갖습니다.
• 무기 카드는 공격력과 내구도를 갖습니다.
• 어떤 카드는 어빌리티를 갖습니다.
• 어떤 카드는 특별한 능력을 갖습니다.
52.
카드 데이터 처리
•하스스톤에는 다양한 카드가 존재하며 데이터가 각각 다릅니다.
• 모든 카드는 마나 코스트를 갖습니다.
• 하수인 카드는 공격력과 체력을 갖습니다.
• 무기 카드는 공격력과 내구도를 갖습니다.
• 어떤 카드는 어빌리티를 갖습니다.
• 어떤 카드는 특별한 능력을 갖습니다.
• 다양한 카드 데이터를 어떻게 가져올 수 있을까요?
카드 효과 처리
•하스스톤에는 고유한 효과를 갖는 카드가 많습니다.
• 일부 하수인들은 능력을 발동하려면 조건이 필요합니다.
58.
카드 효과 처리
•하스스톤에는 고유한 효과를 갖는 카드가 많습니다.
• 일부 하수인들은 능력을 발동하려면 조건이 필요합니다.
• 일부 하수인들은 필드에 낼 때 효과를 발동합니다.
59.
카드 효과 처리
•하스스톤에는 고유한 효과를 갖는 카드가 많습니다.
• 일부 하수인들은 능력을 발동하려면 조건이 필요합니다.
• 일부 하수인들은 필드에 낼 때 효과를 발동합니다.
• 일부 하수인들은 죽을 때 효과를 발동합니다.
60.
카드 효과 처리
•하스스톤에는 고유한 효과를 갖는 카드가 많습니다.
• 일부 하수인들은 능력을 발동하려면 조건이 필요합니다.
• 일부 하수인들은 필드에 낼 때 효과를 발동합니다.
• 일부 하수인들은 죽을 때 효과를 발동합니다.
• 비밀은 특정 조건에 부합하는 상황이 일어나면 저절로 발동합니다.
61.
카드 효과 처리
•하스스톤에는 고유한 효과를 갖는 카드가 많습니다.
• 일부 하수인들은 능력을 발동하려면 조건이 필요합니다.
• 일부 하수인들은 필드에 낼 때 효과를 발동합니다.
• 일부 하수인들은 죽을 때 효과를 발동합니다.
• 비밀은 특정 조건에 부합하는 상황이 일어나면 저절로 발동합니다.
• 다양한 카드 효과를 어떻게 처리할 수 있을까요?
62.
카드 효과 처리
•‘카드 데이터 처리’를 설명하면서 이런 말을 했었습니다.
(카드의 동작을 구현하는데 필요한 (거의) 모든 데이터가 있습니다.)
• 이제 (거의)에 대한 이야기를 해봅시다.
• 앞에서 봤던 ‘리로이 젠킨스’ 카드를 다시 한 번 살펴봅시다.
63.
카드 효과 처리
{
"id":"EX1_116",
"name": "Leeroy Jenkins",
"text": "Charge. Battlecry: Summon two 1/1 Whelps for your opponent.",
"attack": 6,
"cardClass": "NEUTRAL",
"collectible": true,
"cost": 5,
"elite": true,
"faction": "ALLIANCE",
"health": 2,
"mechanics": [
"BATTLECRY",
"CHARGE"
],
"rarity": "LEGENDARY",
"set": "EXPERT1",
"type": "MINION"
}
64.
카드 효과 처리
•‘카드 데이터 처리’를 설명하면서 이런 말을 했었습니다.
(카드의 동작을 구현하는데 필요한 (거의) 모든 데이터가 있습니다.)
• 이제 (거의)에 대한 이야기를 해봅시다.
• 앞에서 봤던 ‘리로이 젠킨스’ 카드를 다시 한 번 살펴봅시다.
• 카드 데이터로는 카드의 효과를 읽을 수 없습니다.
65.
• 또한 하스스톤에는수많은 카드가 존재합니다.
• 여러분이 구현해야 할 카드는 생각보다 많습니다.
• 지금까지 하스스톤에서 출시된 카드의 장수는 1,926장입니다.
• 정말 1,926장 뿐일까요?
카드 효과 처리
• 또한 하스스톤에는수많은 카드가 존재합니다.
• 여러분이 구현해야 할 카드는 생각보다 많습니다.
• 지금까지 하스스톤에서 출시된 카드의 장수는 2,061장입니다.
• 정말 1,926장 뿐일까요?
• 수집 가능 여부와 관계없이 게임 내에서 사용할 수 있는 카드를 모두 구현해야 합니다.
• 그 외 모험 모드에서 사용하는 카드와 내부 테스트용 카드를 합치면 무려 7,047장이나 됩니다.
• 카드의 효과를 쉽게 구현할 방법이 없을까요?
카드 효과 처리
카드 효과를 구현하려면
•Latent Predictor Networks for Code Generation (2016)
https://arxiv.org/pdf/1603.06744.pdf
71.
카드 효과를 구현하려면
•첫번째 방법 : 카드를 읽어 효과를 자동으로 생성하는 방법을 찾아봅니다.
• 선행 연구가 있긴 한데 쉽지 않고 올바르게 생성이 되지 않는 경우도 많습니다. (실패)
• 두번째 방법 : 카드마다 효과를 직접 구현합니다.
• 정말로 하기 힘들지만 가장 확실한 방법입니다.
• 다행히 효과가 없는 카드도 존재하기 때문에 6,086장까진 구현하지 않아도 됩니다.
72.
카드 효과의 종류
•전투의 함성 : 플레이어가 카드를 핸드에서 낼 때 한 번만 발동하는 효과
• 죽음의 메아리 : 하수인이나 무기가 파괴될 때 발동하는 효과
73.
카드 효과의 종류
•인챈트(Enchant) : 지정된 대상에게 특정 효과를 부여
• 오라(Aura) : 하수인이 필드에 있는 동안 지정된 대상에게 지정된 효과가 유지
전투의 함성 /죽음의 메아리 구현
• 태스크를 만들어 처리합니다.
• 태스크를 만들기 위해서는 먼저 부모 클래스 ITask를 상속받아야 합니다.
• 그리고 순수 가상 함수 GetTaskID()와 Impl()을 구현해야 합니다.
• GetTaskID() : 태스크 ID를 반환하는 함수
• Impl() : 태스크 로직을 구현하는 함수
• 구현한 태스크는 Run() 또는 RunMulti() 함수를 호출해 실행할 수 있습니다.
• Run() : 하나의 태스크를 실행하는 함수
• RunMulti() : 여러 태스크를 연속으로 실행하는 함수
76.
전투의 함성 /죽음의 메아리 구현
• ITask 클래스
class ITask
{
public:
TaskStatus Run(Player& player);
virtual TaskID GetTaskID() const = 0;
private:
virtual TaskStatus Impl(Player& player) = 0;
};
전투의 함성 /죽음의 메아리 구현
• 예제로 카드 ‘방패 올리기’를 구현해 봅시다.
• 카드를 보고 어떤 태스크를 만들어야 할 지 생각해 봅시다.
79.
전투의 함성 /죽음의 메아리 구현
• 예제로 카드 ‘방패 올리기’를 구현해 봅시다.
• 카드를 보고 어떤 태스크를 만들어야 할 지 생각해 봅시다.
• 이 카드는 2종류의 태스크가 필요합니다.
• Gain 5 Armor → ArmorTask
• Draw a card → DrawTask
• ArmorTask는 방어도를 얼마나 올릴 것인지 지정합니다.
• DrawTask는 카드를 몇 장이나 드로우할 것인지 지정합니다.
80.
전투의 함성 /죽음의 메아리 구현
• ArmorTask 클래스 : 방어도를 획득합니다.
class ArmorTask : public ITask {
public:
explicit ArmorTask(int amount);
TaskID GetTaskID() const override;
private:
TaskStatus Impl(Player& player) override;
int m_amount = 0;
};
// ---------------------------------------- SPELL- WARRIOR
// [EX1_606] Shield Block - COST:3
// - Faction: Neutral, Set: Core, Rarity: Free
// --------------------------------------------------------
// Text: Gain 5 Armor.
// Draw a card.
// --------------------------------------------------------
power.ClearData();
power.AddPowerTask(new ArmorTask(5));
power.AddPowerTask(new DrawTask(1));
cards.emplace("EX1_606", power);
전투의 함성 / 죽음의 메아리 구현
• 구현한 태스크를 통해 카드 효과를 등록합니다.
86.
카드 동작 테스트
•카드 효과를 구현했다면 의도한 대로 동작하는지 테스트해봐야 합니다.
• 카드의 동작을 확인하기 위한 시나리오를 작성합니다.
• 1. 플레이어 1의 손패에서 카드 ‘방패 올리기’를 냅니다.
• 2. 플레이어 1의 영웅 방어도가 5 증가했는지 확인합니다.
• 3. 플레이어 1의 손패에서 카드 장 수가 그대로인지 확인합니다.
(카드 ‘방패 올리기’를 내서 1장 감소, 카드를 드로우해서 1장 증가)
87.
카드 동작 테스트
•카드 ‘방패 올리기’ 동작 테스트
TEST(CoreCardsGen, EX1_606) {
GameConfig config;
config.player1Class = CardClass::WARRIOR;
config.player2Class = CardClass::WARLOCK;
config.startPlayer = PlayerType::PLAYER1;
config.doFillDecks = true;
config.autoRun = false;
Game game(config);
game.StartGame();
game.ProcessUntil(Step::MAIN_START);
88.
카드 동작 테스트
•카드 ‘방패 올리기’ 동작 테스트
Player& curPlayer = game.GetCurrentPlayer();
Player& opPlayer = game.GetCurrentPlayer().GetOpponent();
curPlayer.maximumMana = curPlayer.currentMana = 10;
opPlayer.maximumMana = opPlayer.currentMana = 10;
const auto card1 = Generic::DrawCard(
curPlayer, Cards::GetInstance().FindCardByName("Shield Block"));
Task::Run(curPlayer, PlayCardTask::Spell(curPlayer, card1));
EXPECT_EQ(curPlayer.GetHand().GetNumOfCards(), 5u);
EXPECT_EQ(curPlayer.GetHero()->GetArmor(), 5);
}
영웅/하수인 공격 구현
•공격하기 전에
• 영웅이나 하수인이 공격할 수 있는지 확인합니다.
• 공격할 대상이 유효한지 확인합니다.
• 공격할 때
• 빙결, 독성, 은신, 돌진 등의 특수 능력이 있는지 확인합니다.
• 피해를 주고 난 뒤 남은 체력에 따라 영웅/하수인을 파괴합니다.
91.
void Attack(Player& player,Character* source, Character* target) {
// Check source can attack and target is valid
if (!source->CanAttack() ||
!source->IsValidCombatTarget(*player.opponent, target))
return;
// Activate attack trigger
player.GetGame()->triggerManager.OnAttackTrigger(&player, source);
player.GetGame()->ProcessTasks();
// Set game step to MAIN_COMBAT
player.GetGame()->step = Step::MAIN_COMBAT;
...
92.
bool Character::CanAttack() {
//If the value of attack is 0, returns false
if (GetAttack() == 0)
return false;
// If the character is frozen, returns false
if (GetGameTag(GameTag::FROZEN) == 1)
return false;
// If the character is exhausted, returns false
if (GetExhausted())
return false;
//! If the character can't attack, returns false
if (GetGameTag(GameTag::CANT_ATTACK) == 1)
return false;
return true;
}
93.
std::vector<Character*> Character::GetValidCombatTargets(Player& opponent)const {
bool isExistTauntInField = false;
std::vector<Character*> targets, targetsHaveTaunt;
for (auto& minion : opponent.GetField().GetAllMinions()) {
if (minion->GetGameTag(GameTag::STEALTH) == 0) {
if (minion->GetGameTag(GameTag::TAUNT) == 1) {
isExistTauntInField = true;
targetsHaveTaunt.emplace_back(minion);
continue;
}
if (!isExistTauntInField)
targets.emplace_back(minion);
}
}
if (isExistTauntInField)
return targetsHaveTaunt;
if (GetGameTag(GameTag::CANNOT_ATTACK_HEROES) == 0 &&
opponent.GetHero()->GetGameTag(GameTag::IMMUNE) == 0 &&
opponent.GetHero()->GetGameTag(GameTag::STEALTH) == 0)
targets.emplace_back(opponent.GetHero());
return targets;
}
94.
하스스톤의 게임 진행
BEGIN_FIRSTBEGIN_SHUFFLE BEGIN_DRAW BEGIN_MULLIGAN
MAIN_START
_TRIGGERS
MAIN_READYMAIN_RESOURCEMAIN_DRAW
MAIN_START MAIN_ACTION MAIN_COMBAT MAIN_END
MAIN_CLEANUPMAIN_NEXTFINAL_WRAPUPFINAL_GAMEOVER
95.
하스스톤의 게임 진행
BEGIN_FIRSTBEGIN_SHUFFLE BEGIN_DRAW BEGIN_MULLIGAN
• BEGIN_FIRST : 게임을 시작할 때 처리해야 할 로직을 수행합니다.
• BEGIN_SHUFFLE : 덱에 있는 카드들을 무작위로 섞습니다.
• BEGIN_DRAW : 카드를 뽑습니다. (선 플레이어 : 3장, 후 플레이어 : 4장 + 동전)
• BEGIN_MULLIGAN : 마음에 들지 않는 카드를 선택해 덱에 넣고 다시 뽑습니다.
MAIN_ACTIONMAIN_START MAIN_COMBAT MAIN_END
하스스톤의게임 진행
• MAIN_START : 플레이어의 턴을 시작할 때 처리해야 할 로직을 수행합니다.
• MAIN_ACTION : 플레이어의 행동을 처리합니다. (예 : 카드를 낸다, 턴을 종료한다)
• MAIN_COMBAT : 하수인이 상대편의 하수인이나 영웅을 공격합니다.
• MAIN_END : 플레이어의 턴이 끝날 때 처리해야 할 로직을 수행합니다.
4. 하스스톤 강화학습환경 개발
• MCTS(Monte-Carlo Tree Search)
• 보드(Board) 클래스
• PyTorch C++ API 연동
• Python API 지원
100.
하스스톤 강화학습 환경개발
• 지금까지 우리는 하스스톤 게임을 만들었습니다.
하지만 우리의 목표는 AI가 하스스톤 게임을 하도록 만드는 것입니다.
• 따라서 AI가 게임을 플레이 할 수 있게 만들어야 합니다.
• 현재 게임의 상황을 AI에게 전달합니다.
• AI가 정책에 따라 어떤 행동을 할 것인지를 결정합니다.
• AI가 결정한 행동에 따라 게임을 진행합니다.