[C++] std::variantを使ってシミュレータでオートプレイさせる

この記事は C++ Advent Calendar 2023 24日目の記事です。

ゲームドメインでのお話です
少し前にC++でAIシミュレータ作ってオートプレイさせてみたので、どんだけの人に刺さる内容かは判らないですが
自分の振り返り的に紹介します

前半が、シミュレータについての話で、
後半が、オートプレイの時に std::variant が良い感じだった話
です

記載量が多くなってしまいました
前半はC++に直接関係のない話なので、読み物として見て頂ければと思います!

シミュレータとは

シミュレータと聞いてピンとくる方は少ないと思います
実際、自分自身もあまり意味が判っていませんでした

きっかけは、ゲームで機械学習を取り入れるには、シミュレータが必要と聞いたことが始まりです

自分なりにまとめてみると、シミュレータには観点が3つあることに気が付きました

■ AI学習観点

何らかの学習系AIを導入しようと思ったら、必ずシミュレータが必要になります
そして学習するだけでなく、シミュレータでのオートプレイが出来ると以下のように色々と夢も広がります

■ テスト観点

こんな感じで
シミュレータがあると、テスト関係の人たちも喜ぶわけです

■ リプレイ観点

つまりシミュレータを作ることが出来たら、

に有効なネタとなりそうです

シミュレータを作るのはちょっと工夫が必要

実際にシミュレータを作るには
周りからの理解も必要であり、性能面でもシビアな要求が求められます

例えば、学習系のAIモデルを作るためのシミュレータであれば、億の回数くらいのバトルはしゃっと実行して欲しいところです
1バトルシミュレーションするのに1分かかります~では話にならないのです
それだと2日経っても3000バトルすらこなせないことになります

そもそも、「●●を入力したら必ず▲▲の結果になること!」という 決定論的シミュレータ を作るのは 最初から設計を考えておく必要があり、後付けでは難しいです

また、キー情報を入力値としてリプレイさせるのは昔からよくある手法だと思いますが
後々のこと(学習系で利用)を考えると、入力値は「キー」ではなく、「状態」が好ましいです

簡単にターン制バトルで言うとこんな感じ

<入力>
「勇者」が「鉄の盾」「鉄の剣」を装備して
「はぐれメタル」に「剣で攻撃」をした  
(攻撃力10)

<それを受けた出力>
「はぐれメタル」が(内部防御力9で)
「ダメージ-1」を受けた  
(そして逃げるフラグが立った)  
※(カッコ)の中は内部パラメータなので、ゲーム画面では見えない情報

こんな感じで「バトルを直接的に変化させる内部状態を入出力の対象にする」のが良いです
「状態」であれば学習系でも利用できるし、何より人が見ても読めます(キー入力よりは…)

→実際に目検で状態を読むのは困難ではありますが…

他にも大量のログを管理する方法なども含めて「シミュレータそのもの」を作るコツはたくさんあるのですが
今回の話題からはちょっと外れるのでこのへんにしときます

作ったシミュレータを活躍させる方法

ヤッター!シミュレータが出来たぞー
同じログを入力したら、同じ結果になったぞー!

と喜んでいるのもつかの間…

苦労して作ったのですが、もちろんそれだけではとある部品が出来ただけなので、何にも良いことはありません

何かの目的があってシミュレータを作ったのでしょうから、それに向かって突き進むべきなのですが
最初に成果が出やすくて、チームに役に立ったと思ってもらえるのは
上に挙げた3つ(Ai学習/テスト/リプレイ)の中でも特に、テスト観点での活用 が良いように思います

テスト観点であれば、本体プロダクトとは別で話を進めやすく、またシミュレータ作成側のエンジニアのみで作業の完結も出来るので、おすすめです

そこでやっと今回の本題に来ました

オートプレイ です!

強化学習にしろ機械学習にしろ、オートプレイは必須!
それがテストにも転用できる!

となれば、時間のかかる学習系を粛々と進めつつ
直近はテスト目的でシミュレータを活躍させるのが良いと思います

→もちろんプランナーが乗り気でリプレイ機能を求めているなら、そっちを作ってください

みんなが学習系に乗り気で、結果をすぐに出したいと思っていても
残念ながらなかなか早期に学習系の結果は出ないのです…
R&D要素の大きく、学習させても期待値に満たない可能性もあります

なのでAI学習目的でシミュレータを作り始めたとしても、テスト活躍案は持っておいた方が良いです

しかもテストでの実績が目に見えて出てくると
リプレイ観点の設計もぐっと提案しやすくなり、録画機能やチート対策などにも話題が広がると思います

→このリプレイ観点の話題は、GDCセッションでもよく出てます

(本当は学習系で成果がポンと出せるのが一番良いのですがね…)

ただし、テスト観点のみで走り進めてしまうと、リプレイ出来ない設計になってしまうこともあります
「テストのためにシミュレータでオートプレイをする」のと
「学習のためにシミュレータでオートプレイをする」のと
「リプレイのためにシミュレータでオートプレイをする」のとだと
結果が似てるようで設計思想が異なるので、良い感じの和集合設計を意識しながら作るのもエンジニアの腕の見せ所ではあります

オートプレイの実装1(とりあえず動作した版)

今回はC++のアドカレ記事で、やっとC++の話が出てきます
長い前置きでしたが、お待たせしました

<前提>

状況はこんな感じで、実際に実装してみます

<実装例>

main() で動作するように、1ターンだけオートプレイとして動作するバージョンを例に書いてみました
Conpiler Explorerに全く同じものをアップしています

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
// ---------------------
// main.cpp

#include <functional>
#include <iostream>
#include <vector>
#include <memory>

enum class CharactorId
{
    Player,       // プレイヤー
    FriendAlice,  // 味方
    FriendBob,
    EnemyEve,     // 以下は敵
    /* ... */
};

// 技の種類
enum class OrderType
{
    Attack,    // 攻撃
    Buff,      // バフ
    Summon,    // 召喚
    /* ... */
};

// 実際にそのターンでどんな技を出すのかの情報(★0)
class AutoPlayOrder
{
public:
    AutoPlayOrder(CharactorId id, OrderType type)
    {
        charactorId_ = id;
        CurrentOrderType = type;

        // enumでのタイプ分け(★1)
        switch (CurrentOrderType)
        {
        case OrderType::Attack:
            hitPoint_ = 10;     // default
            ExecuteFunction = [this]() {ExecuteAttack(); };
            break;
        case OrderType::Buff:
            buffPoint_ = 5;     // default
            ExecuteFunction = [this]() {ExecuteBuff(); };
            break;
        case OrderType::Summon:
        default:
            summonFriendId_ = CharactorId::FriendBob;   // default
            ExecuteFunction = [this]() {ExecuteSummon(); };
            break;
        }
    }

    OrderType CurrentOrderType;
    std::function<void()> ExecuteFunction;
    void ExecuteAttack() const { std::cout << "Attack:" << hitPoint_ << "\n"; }
    void ExecuteBuff() const { std::cout << "Buff:" << buffPoint_ << "\n"; }
    void ExecuteSummon() const { std::cout << "Summon:" << (int)summonFriendId_ << "\n"; }

private:
    CharactorId charactorId_;

    // 技によって必要なメンバ変数が異なる(★2)
    int hitPoint_;                 // orderType == Attack なら、攻撃力
    int buffPoint_;                // orderType == Buff なら、バフ量
    CharactorId summonFriendId_;   // orderType == Summon なら、一緒に召喚する味方ID
    /* ... */

};

// オートプレイの取りまとめクラス
class AutoPlay
{
public:
    std::vector<std::shared_ptr<AutoPlayOrder>> OrderList;
};

int main()
{
    std::cout << "===AutoPlay===\n";

    auto autoPlay = std::make_unique<AutoPlay>();

    // 今回は、とあるターンでの技が3つあったとする
    // 何の技が出せるのかは、今出している技を見たりするので結構複雑な処理になる(★3)
    autoPlay->OrderList.emplace_back(std::make_shared<AutoPlayOrder>(CharactorId::Player, OrderType::Attack));
    autoPlay->OrderList.emplace_back(std::make_shared<AutoPlayOrder>(CharactorId::FriendAlice, OrderType::Summon));
    autoPlay->OrderList.emplace_back(std::make_shared<AutoPlayOrder>(CharactorId::FriendBob, OrderType::Buff));

    // execute
    for (auto const& order : autoPlay->OrderList)
    {
        // オートプレイ実行時はポリモーフィックに出来てる(★4)
        order->ExecuteFunction();
    }
}

出力結果

===AutoPlay===
Attack:10
Summon:2
Buff:5

オートプレイの実装1の考察

これは OrderList に出せる技をバンバン登録して、それをしていくコードです

■新規に登録する用の型が必要(★0)

OrderList というリストに登録するための管理用の型 AutoPlayOrder を新たに作っています

■技にによっては使わないメンバを持っている(★2)

無駄があり、あまり良いコードとは言えないです
そしてこれは、全部の敵や味方が必要な情報を網羅する必要のある神クラスになってしまってます

1
2
3
4
// 技によって必要なメンバ変数が異なる(★2)
int hitPoint_;                 // orderType == Attack なら、攻撃力
int buffPoint_;                // orderType == Buff なら、バフ量
CharactorId summonFriendId_;   // orderType == Summon なら、一緒に召喚する味方ID

ちょっと何とかしたい…

そんな時には、Orderのベースクラスを作って、継承で攻撃クラスを作れば
プレイヤーだけが hitPoint_ のメンバを持てるわけなので
継承するか
数が少ないのであれば、我慢して必要情報を全持ちするかで、とりあえずは動作出来ます

どうしよう…これだけのために継承するのは、依存関係や制約が強すぎる気がします
(今回の場合は継承でリファクタしても悪くないとは思いますが…)

■結局enum判定をがっつりやっている(★1)

技を実行している箇所について↓↓↓

1
order->ExecuteFunction();

ここあたりは、std::function を使ってポリモーフィックで良い感じに見えますが
結局のところ、「何の技を出せる?」という判断をするときに、今時点で何がリストに上がっているか?を調べたくなるので(★3)
(サンプルコードには書いてませんけど)実際はリスト内の OrderTypeenum をめちゃめちゃ判定に利用しています
つまり実行時のみポリモーフィックにしたところで、結局 enum で場合分けすることが多いので、あまりうまみが無くてシュン…って感じ

この状態でもオートプレイの動作に問題は無いのですが、リファクタしていこうと思います

オートプレイの実装2(std::variant版)

継承はあんまり使いたくないという発想のもとで、
とりあえず、コンストラクタ生成時の引数制約で、必要な情報を入手するようにします
今回はやってませんが、共通クラスを作ってコンポジションで強制しても良いと思います

また、enum 判定を減らしたいので、std::varriant を用いて OrderList に既に登録されている技を型判定無しで取得する手段を提供してみます

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// ---------------------
// main.cpp

#include <functional>
#include <iostream>
#include <vector>
#include <memory>
#include <variant>

// ... 既存のenum定義はそのまま記載 ...

// ---------------------------------------------------
// 実際にそのターンでどんな技を出すのかの情報(★0')
// 攻撃
class OrderAttack
{
public:
    OrderAttack(CharactorId id, int hitPoint) :
        CurrentOrderType(OrderType::Attack),
        charactorId_(id),
        hitPoint_(hitPoint) {}

    OrderType CurrentOrderType;
    CharactorId GetCharactor() { return charactorId_; }
    void ExecuteOrder() { std::cout << "Attack:" << hitPoint_ << "\n"; }
    void UpdatePlayerAttack(int newHitPoint){hitPoint_ = newHitPoint;}
private:
    CharactorId charactorId_;
    int hitPoint_;                 // 攻撃力
};
// バフ
class OrderBuff
{
public:
    OrderBuff(CharactorId id, int buff) :
        CurrentOrderType(OrderType::Buff),
        charactorId_(id),
        buffPoint_(buff) {}

    OrderType CurrentOrderType;
    CharactorId GetCharactor() { return charactorId_; }
    void ExecuteOrder() { std::cout << "Buff:" << buffPoint_ << "\n"; }
private:
    CharactorId charactorId_;
    int buffPoint_;                 // バフ量
};
// 召喚
class OrderSummon
{
public:
    OrderSummon(CharactorId id, CharactorId summonFriend) :
        CurrentOrderType(OrderType::Summon),
        charactorId_(id),
        summonFriendId_(summonFriend) {}

    OrderType CurrentOrderType;
    CharactorId GetCharactor() { return charactorId_; }
    void ExecuteOrder() { std::cout << "Summon:" << (int)summonFriendId_ << "\n"; }
private:
    CharactorId charactorId_;
    CharactorId summonFriendId_;            // 一緒に召喚する味方ID
};

// ---------------------------------------------------
// キャラクターIDを取得するための構造体
struct GetCharactorId
{
    void operator()(const std::shared_ptr<OrderAttack>& order) const { order->GetCharactor(); }
    void operator()(const std::shared_ptr<OrderBuff>& order) const { order->GetCharactor(); }
    void operator()(const std::shared_ptr<OrderSummon>& order) const { order->GetCharactor(); }
};

// 技の実行をまとめている構造体
struct ExecuteOrder
{
    void operator()(const std::shared_ptr<OrderAttack>& order) const { order->ExecuteOrder(); }
    void operator()(const std::shared_ptr<OrderBuff>& order) const { order->ExecuteOrder(); }
    void operator()(const std::shared_ptr<OrderSummon>& order) const { order->ExecuteOrder(); }
};

// ---------------------------------------------------
// オートプレイの取りまとめクラス
class AutoPlay
{
public:
    std::vector<std::variant<std::shared_ptr<OrderAttack>, std::shared_ptr<OrderBuff>, std::shared_ptr<OrderSummon>>> OrderList;

    // このターンで実行したい、全部のオートプレイの技を実行する
    void ExecuteAllOrder()
    {
        for (auto const& order : OrderList)
        {
            std::visit(ExecuteOrder{}, order);
        }
    }

    // 特定の何かの値を更新したい
    // 例)プレイヤーの攻撃力をあとから更新
    void UpdatePlayerAttackPoint(int newHitPoint)
    {
        for (auto& order : OrderList)
        {
            // OrderAttackオブジェクトを探す
            auto attackOrder = std::get_if<std::shared_ptr<OrderAttack>>(&order);
            if (attackOrder && (*attackOrder)->GetCharactor() == CharactorId::Player)
            {
                (*attackOrder)->UpdatePlayerAttack(newHitPoint);  // (★3')
                break;
            }
        }
    }
};

int main()
{
    std::cout << "===AutoPlay===\n";

    auto autoPlay = std::make_unique<AutoPlay>();

    // 何の技が出せるのかは、今出している技を見たりして登録していく
    autoPlay->OrderList.emplace_back(std::make_shared<OrderAttack>(CharactorId::Player, 10));
    autoPlay->OrderList.emplace_back(std::make_shared<OrderSummon>(CharactorId::FriendAlice, CharactorId::FriendBob));

    // 例えば、ここの時点で何かバフがかかったとして、既にPlayerに登録されている値をこの時点で更新したい、という時
    autoPlay->UpdatePlayerAttackPoint(20);

    autoPlay->OrderList.emplace_back(std::make_shared<OrderBuff>(CharactorId::FriendBob, 5));

    // 実行したい技が整ったので、実行!
    autoPlay->ExecuteAllOrder();
}
===AutoPlay===
Attack:20
Summon:2
Buff:5

先ほどは OrderType のenumを使って、技の処理を振り分けしていたのですが

1
struct GetCharactorId{}

これをうまく使うことによって、場合分けしなくて済むようになりました

さっきは、std::function のおかげで実行時のみは、かろうじてポリモーフィックに動作していましたが、std::variant のおかげで、他の処理も技の型( OrderType で持っていたもの)に依存せずに記載することができました(★3’)

また、struct GetCharactorIdstruct ExecuteOrder を定義する必要があるものの、class OrderAttack はインタフェースや継承を持っていないので、なんだか自由を手に入れた気分になります

std::variant を使ってみると、こんな書き方が出来るのか~という自分なりの新たな発見でした

終わりに

今回、シミュレータを作った際に、色々と勉強になることがあったので、ひっくるめて記載してみました

シミュレータという機能がニッチながらも、ゲーム開発においては重要なポイントになりそうなこと、
また今回記載した std::variant を使って書いた処理は Visitor Pattern というらしいですが
継承、インタフェース、コンポジションなどなど、やりながらあれ?これは?という箇所がたくさんあったので、そういったコードを試行錯誤できたことなどが良い経験だったなーと思って書きました

自分としては継承を使った記載方法でも、今回は良さそうだなと思ったし、
最初に記載した、 AutoPlayOrder という手続き的な神クラスの記載方法も、技の数が少ないなど状況によってはそこまで悪くないかもなと思いました

シミュレータを突き詰めていくと、技や手札やゲーム環境などのパラメータ追加の容易さと、実行時にいかに高速に動作することが求められてくるので
そうなっていくとまた設計思想も変わってきそうだなとも思いましたが、今回はこのへんまでで終わろうと思います!

参考

std::variant に気が付けたのは、CppCon2022の Klaus Iglberger さんの
「Breaking Dependencies - The Visitor Design Pattern in Cpp」
というセッションを見て、おおおおおーと思ったからです

C++を言語にしたデザインパターンの書籍も出されています

この著書の中で、ベースクラスそんなに気軽に更新できないし、純仮想関数を強いた日には同僚とのバーベキューパーティに呼ばれなくなるかもしれないよ!という注釈が何度も出てきて、個人的にウケました


[US配列] 再起動なしにUS配列に変更するULE4JIS

キーボードレイアウトはみなさん何を使ってますか?

最近はリモートワークも加速したため、わたしは私用でも会社でもハイスペックゲーミングノートPCを利用するようになりました
(持ち運べるので)

わたしはノートPCのキー配列は、たいてい、日本語配列を購入します

でも、外付けキーボードを用いて作業するときは、US配列にしています

これは昔に、人のPCで何かデバッグしたりとか、限られたサーバールームなどの環境で作業したりする際に
キー配列でイラッとして打ち間違い多発とかがよくあったのです

そんなときでも違和感なくキー操作できるように
普段からあえて、どちらのキー配列でも作業できるようにしといたほうが良いかなーと思い
常時(US/JIS)配列に触れる環境をあえて作っています

やり始めた当初は、通常作業が相当ストレスフルだったのですが
今となっては慣れたもんで、Winwodでも macOSでも、US配列でも日本語配列でも
なんでもすんなりと受け入れられるようになりました

日常のコツで、パスワードの記号は、両方の配置を確実に覚えられるものにしておくか、覚えられない位置の記号は利用しないか、という行動にもつながりました…

ともあれ、ノートについてるのは日本語配列、外付けキーボードはUS配列、で日によってちょこちょこと行き来したいとき、キー配列の変更のためにわざわざ再起動をするのはとても腰が重いです

そこで、キー配列のエミュレータをしてくれる常駐アプリをいつも入れています

サクサクと軽いです
インストールも必要ないし、変なランタイムにも依存してません

で、この ULE4JIS を起動している時だけUS配列になります
(US配列を辞めたいときは常駐終了させるか
 タスクバーのアイコンから「エミュレーション停止」をするかで簡単に変更できる)

再起動が不要!!!これ超重要!!

ずいぶん昔に公開されているツールですが、わたしの Windows11 でも問題なく動作しています

この作者の方に募金したいレベルで毎日使わせてもらってます!
(特に募金も Githubスポンサーもは見当たらない)

いつもありがとうございます!!!

と声を大にして言いたいと思って、ブログ書きましたmm


[C++] C++の新しいパターンマッチ「inspect」

この記事は meetup app osaka@7 online への参加記事です

先日 2023/2/20 ごろにISO WG21 C++ 委員会メンバーのAntony PolukhinさんがMedium(Yandexサイト)で 「C++23が完成したよ~」と記事を書かれていました

それでちょっと新しいC++の機能には何があるのかなと調べていたところ
知らない文法が増えそうな予感の「Pattern Matching」inspect を見つけたので
それについてメモしてます

これはどこのC++に入るかは決まってないので、将来的には採用されないかもしれないです


<試した環境>


もともとパターンマッチするといえば、こんな感じで switch case を使ってると思います

1
2
3
4
5
6
7
8
9
int x(42);
switch (x) {
    case 0: std::cout << "zero"; break;
    case 42: std::cout << "forty-two"; break;
    default: std::cout << "something else";
}

// output
// "forty-two"

これが、inspect を用いるとこんな感じに書けます

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// number
int x(42);
inspect(x) {
    0 => { std::cout <<  "zero"; }
    42=> { std::cout <<  "forty-two"; }
    _=> { std::cout <<  "something else"; }
};

// output
// "forty-two"

文字列マッチもこんな感じに

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// string
std::string s = "foo";
inspect (s) {
    "foo" => { std::cout << "got foo";}
    "bar" => { std::cout << "got bar";}
    __ => { std::cout << "don't care";}
};

// output
// "foo"

タプル的な値のマッチも比較的少ない行数で書ける

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// tuple
auto box = std::pair(0, 42);
auto&& [i, j] = box;
inspect (box) {
    [0, 0]=> { std::cout << "all zero";}
    [1, 0]=> { std::cout << "i=1, j=0";}
    [0, 1]=> { std::cout << "i=0, j=1";}
    [__, __]=> { std::cout << i << ',' << j;}
};

// output
// 0,42

ポリモーフィックに型判定も出来るらしい
というのも、現時点ではまだ実装されていないみたいで動作しなかったです

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// polymorphic type
struct Fruits { virtual ~Fruits() = default; };
struct Orange : Fruits { int ore; };
struct Apple : Fruits { int ap; };
auto o = new Orange();
inspect (o)
{
//    2023/3/3 時はコンパイルエラー
    <Orange> => { std::cout <<  "Orange"; }
    <Apple>  => { std::cout <<  "Apple"; }
};

これが出来れば、あとはテンプレートのオペレーター演算子も比較的少ない行数で実装できるという…


まだまだ未来の機能ですが、差分学習しておかないと完全に置いて行かれますね…
C++の言語機能は複雑すぎます

とはいえ業務コードでも、競プロなどでも
少ない行数で書けるのはとても良いと思うので(=バグの入る余地が減る)
どんどん進化していってもらいたいですね!

<参考>


[C#] $文字を置換したい

ちょっとnewbie的ですが、$ 文字をひっかけようとしてうまくいかなかったので、その時のメモです

今回の開発環境はこちら


Unityで string 文字列の中の $ 文字を置換使用を思いましたが、うまく出来ませんでした

1
text = text.Replace("\\$", "");

はて。。。なんでかな?

調べてみて、こちらなら思った通りの動作になりました

1
text = Regex.Replace(text, "\\$", "");

正規表現だとうまく行くみたい

参考:


[C++] VC++でHackerRankの環境構築

この記事は C++ Advent Calendar 2022 7日目の記事です。

(内容まとめ)
競プロ勢にとったら当たり前のローカル環境設定についての記事です
自分の言葉でまとめなおしたメモになります
みんな!AtCoder もいいけど HackerRank も面白いよ!


今年の3月ごろ、同僚(海外の方)から
「HackerRank 結構いいよ、わたしは週末によくやってるよ~」
と教えてもらいまして
たまにポチポチ解いています

ざっくり言うと、お題に対するコードを書いて、要求通りの出力が出来れば Pass! でポイントが溜まっていってバッチがもらえたりします
わたしは主にC++の Problem Solving を解いています
(数学系の問題も多いです)

C++ language のジャンルで解くことも出来ますが、こっちは文法問題なので、あまり面白くない

HackerRankは他のオンラインコーディングプラットフォームとはちょっと異なっていて
競技で問題を解いてランクを上げるというよりは、スキルの習得に焦点を当てているような気がします
解いて欲しい問題用の関数がミニマムに提供されてて、最初に記載するであろう入力系の処理が予め記載されているので、解くのに集中出来ますね

で、それまで真面目に Easy 問題を解いていたのですが、簡単すぎるし量も多い…つまらん…
となって、教えてくれた同僚に
「HackerRank、何の問題を解いてる?」
と聞いたところ、
「えぇー Easy やってるの?時間の無駄だよ~わたしは Hard しかやらないよ」
と突っ込まれました><。

さてそこで、Hard 問題をやり始めましたが、あら。。。難しい。。。
こんなけ難しかったらデバッグしたくなるんだけど、HackerRankのサイトに組み込まれたコードエディタ上から問題を解くので、プリントデバッグしか方法がない
変数の中身や配列の中身をもっとアグレッシブに確認したい…

前置きが長くなりましたが、HackerRankをやるときに、Visual Studio上でコードを書いて確認する方法を記載しましたmm

<今回の環境>


VSにコードコピペしてHackerRankの問題を解きたい

コピペ出来ないとしんどいです!

しかし HackerRank も他の競プロと同じく、#include <bits/stdc++.h> があります
そのまんまじゃリンク通りません

なので、ライブラリを認知する箇所に stdc++.h を置いておけば良いのです

他にも色んな方が公開されてますけど、検索するとこの方の↑↑↑リンクがよく見つかりました

これを、VC++がデフォで認知する場所に置きます

一番簡単な場所に調べ方は、

1
#include <iostream>

とコードに書いて、F12iostream の場所までジャンプ
→ソースのタブを右クリックすると、そこのディレクトリの場所までジャンプ出来る

ちなみにわたしの場合は、ここにありました
C:\Program Files (x86)
 \Microsoft Visual Studio
  \2019
   \Enterprise
    \VC
     \Tools
      \MSVC
       \14.29.30133
        \include

エラーやワーニングが出る場合

めんどいですが、毎回エラー除去のための define を書くのが無難な対応だと思います
(業務のコードじゃないし)

例えば、HackerRank でよく出てくるこんなコード

1
ofstream fout(getenv("OUTPUT_PATH"));

エラーになります

error C4996: ‘getenv’: This function or variable may be unsafe. Consider using _dupenv_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.

あんた getenv は非推奨だけど使いたいなら、_CRT_SECURE_NO_WARNINGS 定義しなさいよ!ってやつです
ええ、使いたいんです、HackerRank にコードコピペしたいんで!
なのでわたしはガッツリと先ほどの stdc++.h の上の方に定義を追記しています

自分はVSに慣れてるので、慣れた環境でコード書けるのが快適ですね

(余談) VSCodeでやろうとしたが…

最初は、Windoows の VSCode で環境構築をしようとしてたのですが、なかなか手ごわいです…

mac はさくっとものの数分で C++デバッグ環境が構築できたのに
Windows だと、半日かけてもうまく動きませんでした><。
(VC++ がうまく動かなくて、gcc はなんとか動作したって感じです)

色々調べていた時に、VSCode on Windows 情報でいちばん面白かったのが、この中国の投稿サイトでした

わたしは中国語はよくわからないので、Googleのページ翻訳でよくここの Zhihu サイトを見るのですが
この記事は見てて笑っちゃいました。。。
情報量も多いし、皮肉アリ、ぶっちゃけトークありで、読み物として面白かったです

ここを見て、あ、VSCode on Windows でVC++使うのちょっと大変かも…と思いました
中国は同じように感じがあるので、そこの悩みも詳細にかかれてて、親近感沸きました

あんまり日本でこういう記事見ないな~なんでだろ、ぶっちゃけすぎるからかな?

C++だけじゃなくてUnityの情報も多いです、知識投稿サイトってやつかなー?とにかく面白い


(まとめ)

コード書くなら開発環境は整えようね!

C++ で mac なら VSCode だけど、Win なら VS 使おうぜ!

HackerRank ほんとお勧め、地道に解くのが楽しい


(参考)


[Git] コミット済みのファイルを Git LFS 管理対象にしたいとき

バイナリファイルコミットしちゃたけど、これ Git LFS 管理対象にしたかったよねー。。。

というときの作業メモです

わたしの環境は Windows での動作確認になります

1) git lfs installGit LFS をインストールします
わたしの環境にはすでに入っていたみたい…

>git lfs install
Updated Git hooks.
Git LFS initialized.

2) git lfs version でバージョンを確認しておく

>git lfs version
git-lfs/3.2.0 (GitHub; windows amd64; go 1.18.2)

3) git lfs track とすると、.gitattributes に格納されている対象データの情報が見れます

例えばこんな感じ

>git lfs track
Listing tracked patterns
    *.dll (.gitattributes)
    *.exe (.gitattributes)

4) git lfs track でLFS管理に入れたいファイルを指定します

>git lfs track "*.o"
Tracking "*.o"

ファイル単体を指定したければ、直接指定すればOK

>git lfs track "aaa/bbb/ccc.bin"

5) .gitattributes に追加した対象ファイルが記載されているので
それをコミット&プッシュすればOK!

6) まだLFS管理してなかったときにコミットしてしまったファイルは
いったん削除コミットをしてから、もう一度コミット&プッシュするのが良さそうでした


(参考リンク)


[Unity] Windows/macOS内にあるUnityの場所

バッチモードとかでUnityを起動させたいとき

Windowsだと

C:¥Program Files¥Unity¥Hub¥Editor¥(バージョン名)¥Editor¥Unity.exe

とまぁ想像のつく位置にいてるんですけど

macOSのとき、あれ?どこにあるんだっけな?になったので、そのメモです

mac の場合はここに居てました

/Applications/Unity/Hub/Editor/(バージョン名)/Unity.app/Contents/MacOS/Unity

(バージョン名) は例えば 2021.3.2f1 とかになります


[Windows] 壁紙のレジストリ設定場所

PCの壁紙にしてる画像データ…どこにあるんだっけ?
デスクトップでは見えてるんだけど…

そんな時は一度、レジストリの中を見てみたら解決するかもしれません

Windows11の環境で見てみました


壁紙のレジストリ設定

  1. regedit でアプリを検索すると Registry Editor が出てきます

  2. HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Wallpapers
    の中に、壁紙として登録されている画像情報のパスが入っています

おお。。。ここにありましたか。。。!

しかし、
元のファイルを消しちゃったけど、デスクトップの壁紙だけは残ってるってときは
どこにあるんでしょうかね…
どなたか知ってる方いらっしゃれば教えてくださいmm


(参考)


[Git] macでgitを初めて使う時

mac で初めてコマンドから git を使う時の設定メモです

git コマンドが使えなかった

Gitをコマンドラインで打つと、こんなエラーが出ました。。。

1
% git 

xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun

開発者ツールが入ってないらしい

Xcode をインストールする

xcode-select --install でインストールします

1
2
3
% xcode-select --install
code-select: note: install requested for command line developer tools
% 

こんな感じでインストールが進みます

実際のダウンロードは、わたしの環境では20分くらい、インストールは10分くらいでした

そしてインストール完了のウィンドウが出てほっ…

Gitコマンドが使えるように

git と打ってみる

1
% git                   

usage: git [–version] [–help] [-C ] [-c =]
[–exec-path[=]] [–html-path] [–man-path] [–info-path]
[-p | –paginate | -P | –no-pager] [–no-replace-objects] [–bare]
[–git-dir=] [–work-tree=] [–namespace=]
[]

無事、コマンドラインから git が使えるようになりました〜


[Hugo] GitHub Actionsでサイトを自動デプロイしたときにCNAMEを残す方法

Hugo に限らないのですが
gh-pages ブランチを利用して GitHub Pages を作った時
自動デプロイで、独自ドメインの設定を記載した CNAME が消えちゃうことがあります

そのトラブルシュートについてです


Hugo 自体の設定もあるのですが、それよりも
元のコンテンツに CNAME ファイルを含めてしまうのがシンプルで簡単です

Hugo ならメインブランチの static ディレクトリ以下に CNAME を設置します

これだけでOK!