ゲーム作りは楽しい

なんか書く

【Git】Git cometで彗星を楽しもう

はじめに

git を使っていてコミットと間違えてコメットしそうになったことはないですか?(ない)
今回は、git commitと間違えてgit cometした際に彗星を表示できるようにして見ました。

git-comet.sh

彗星を表示するスクリプトを作成します。
ついでに流れ星もつけました。

#!/bin/bash

cols=$(tput cols)
lines=$(tput lines)

clear
tput civis

# 背景
screen=()
for ((i=0; i<lines; i++)); do
    screen[i]=$(printf "%${cols}s" " ")
done

# 背景の星
for ((i=0; i<200; i++)); do
    x=$((RANDOM % cols))
    y=$((RANDOM % lines))
    case $((RANDOM % 7)) in
        0) char="" ;;
        1) char="·" ;;
        2) char="" ;;
        3) char="" ;;
        *) char=" " ;;
    esac
    line="${screen[y]}"
    screen[y]="${line:0:x}$char${line:x+1}"
done

# 彗星本体
comet_body=(
"                                              ·*·            ·✦·*"
"                                    ·      ·*··         ··✦·*"
"                                ·      ·*···       ···✦·*"
"                         ·  ··      ·*····    ····✦*"
"                      ·  ···   ··**····  ····✦✦*☆"
"                    · ····  ☆·***········*✦✦✦**"
"                 · *  *····*✦**☆···**✦✦✦✦*"
"           ·  · · *·*··☆**✦****✦✦✦✦✦*··☆  ··"
"          · ·*·*·☆*****☆✦*✦✦✦✦✦✦**···**···"
"         ···****✦**✦✦✦✦·✦*✦✦✦·***···"
"        ·*·*✦*✦✦✦✦✦*✦✦✦✦***···      ····"
"        *☆☆★★★✦✦✦✦✦✦*****☆☆☆✦✦ ····"
"        ·*✦★★★✦✦✦****·☆✦✦☆·····"
"        ·*★★★★☆☆**✦✦✦·····"
"          ········     "
)
height=${#comet_body[@]}
width=0
for line in "${comet_body[@]}"; do
    (( ${#line} > width )) && width=${#line}
done

cy=$(( (lines - height) / 2 ))
cx=$(( (cols - width) / 2 ))
for ((i=0; i<height; i++)); do
    comet_line="${comet_body[i]}"
    for ((j=0; j<${#comet_line}; j++)); do
        char="${comet_line:j:1}"
        if [[ "$char" != " " ]]; then
            line="${screen[cy+i]}"
            screen[cy+i]="${line:0:cx+j}$char${line:cx+j+1}"
        fi
    done
done

# 流れ星初期設定
meteor_count=5
declare -A meteor_x
declare -A meteor_y
declare -A meteor_start

half=$((cols / 2))
quarter=$((cols / 4))

for ((i=0; i<meteor_count; i++)); do
    if (( RANDOM % 2 == 0 )); then
        meteor_x[$i]=$(( quarter + RANDOM % (half - quarter) ))
    else
        start=$(( 5 * quarter ))
        end=$(( 3 * half ))
        (( end >= cols )) && end=$((cols - 1))
        meteor_x[$i]=$(( start + RANDOM % (end - start + 1) ))
    fi
    meteor_y[$i]=0
    meteor_start[$i]=$((10 + RANDOM % 30))  # 開始フレームのズレ
done

tail_length=5

#アニメーション
for ((frame=0; frame<lines+40; frame++)); do
    frame_screen=("${screen[@]}")

    for ((i=0; i<meteor_count; i++)); do
        # 開始フレーム待ち
        if (( frame < meteor_start[$i] )); then
            continue
        fi

        # 流れ星tail描画
        for ((t=0; t<tail_length; t++)); do
            tx=$((meteor_x[$i]+t*3))
            ty=$((meteor_y[$i]-t))
            if (( tx >=0 && tx < cols && ty >=0 && ty < lines )); then
                line="${frame_screen[ty]}"
                frame_screen[ty]="${line:0:tx}·${line:tx+1}"
            fi
        done

        # 流れ星本体
        if (( meteor_x[$i] >=0 && meteor_x[$i] < cols && meteor_y[$i] >=0 && meteor_y[$i] < lines )); then
            line="${frame_screen[meteor_y[$i]]}"
            frame_screen[meteor_y[$i]]="${line:0:meteor_x[$i]}${line:meteor_x[$i]+1}"
        fi

        # 斜め左下に移動
        meteor_x[$i]=$((meteor_x[$i]-3))
        meteor_y[$i]=$((meteor_y[$i]+1))
    done

    # 出力
    tput cup 0 0
    for ((i=0; i<lines; i++)); do
        echo -ne "${frame_screen[i]}"
    done

    sleep 0.005
done

tput cnorm

面倒くさいので、ほぼAIに書かせましたが
彗星のアスキーアートだけはAIが出してきたのがショボすぎたので、自分で作りました。

                       *
                     ***
                   *****
                 *******
               *****
             ***
           *
        ///
     ///
  ///
///

ちょっとショボすぎませんかね…

gitコマンドのエイリアスの作成

.git/configに以下を追加します

[alias]
    comet = !bash ./git-comet.sh

こうすることで
git cometのコマンドで、先ほど作成したgit-comet.shを実行できます。
スクリプトは、リポジトリのルートに置いてあります

実行結果

綺麗ですね🤗

まとめ

  • git commitgit cometと入力ミスすることは、まずないと思います。
  • 作業中に疲れたら彗星を楽しもう。

XをTwitterに戻す方法

はじめに

Xってなんですか?
やっぱりTwitterだよな!

Tampermonkeyの使用

今回はめんどうくさいので
手軽にブラウザ拡張みたいなことができるTampermonkeyを紹介します

chromewebstore.google.com

Tampermonkeyは、自分で作ったJavaScriptコードを好きなウェブページに適用して、ページの見た目や動作を自由にカスタマイズできる拡張機能です。

XをTwitterに戻すユーザースクリプトを作成する

まずは新規作成です。

// ==UserScript==
// @name         Replace X with Twitter
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @description  Replace all visible "X" text with "Twitter"
// @author       tyanmahou
// @match        *://*/*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';
    // タイトル
    document.title = document.title.replace(/x/gi, 'Twitter');
    // 文字列を置換する関数(再帰的)
    function replaceText(node) {
        if (node.nodeType === Node.TEXT_NODE) {
            // "X" を "Twitter" に置き換え
            node.textContent = node.textContent.replace(/x/gi, 'Twitter');
        } else if (
            node.nodeType === Node.ELEMENT_NODE &&
            // styleなどが壊れないように
            !['SCRIPT', 'STYLE'].includes(node.nodeName)
        ) {
            for (let child of node.childNodes) {
                replaceText(child);
            }
        }
    }

    // 初期実行
    replaceText(document.body);

    // 動的に変わる要素にも対応(MutationObserver)
    const observer = new MutationObserver((mutations) => {
        for (const mutation of mutations) {
            for (const node of mutation.addedNodes) {
                replaceText(node);
            }
        }
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });
})();

ユーザースクリプトを作成出来たら保存します。

注意

自分はVivaldiを使っているのですが chrome://extensions から Tampermonkeyの詳細で
ユーザー スクリプトを許可するONにしてないと動かなかったので、ココも確認がいります。

適用されているのを確認

正しく動作していれば、ブラウザ拡張のアイコンにバッジが付きます




まとめ

Tampermonkeyを使って気軽にXをTwitterに戻せた!! やったー!

もう大丈夫!静的と性的を間違えても安心

typoミス

静的関数、静的メソッド…などなど
プログラミングしているなかで、「静的(static) 」という言葉を使うことが、よくありますが
間違えて「性的」に変換ミスしてしまった経験ないでしょうか?

今回は、静的と性的を間違えても大丈夫にする方法を紹介します!

sexual.hpp

以下のようなヘッダーを作成します。

#pragma once
#define sexual static

VisualStudioの場合、 プロジェクトのプロパティから
C/C++ > 詳細設定 > 必ず使用させるインクルードファイル に sexual.hppを登録しておくと、安心です。

※プリコンパイル済みヘッダーに入れておくでも良いでしょう

プログラム例

#include <iostream>

class Hello
{
public:
    sexual void Print()
    {
        std::cout << Message << std::endl;
    }

    constexpr sexual auto* Message = "Hello World";
};

int main() 
{
    Hello::Print();

    return 0;
}

これで、「静的 (static)」を間違えて「性的 (sexual)」にしてしまっても、エラーにならずにすみます!
やったー!

おまけ C++23からのstatic

C++23からは、operator()operator[]をstaticにできるようになります。

struct FunctionObject
{
    // C++23からstaticにできるようになった 
    static auto operator()()
    {
        // something
    }
    static auto operator[]()
    {    
        // something
    }
};

ラムダ式もstatic operator()に対応してます。

auto f = []()static{
    // something
};

constexpr関数の中でstatic constexpr変数が使えるようになります

constexpr const char* Message()
{
  constexpr static auto* str = "Hello World"; // C++23からOK
  return str;
}

結婚したのか、俺以外のヤツと… ベン図

「結婚したのか、俺以外のヤツと…」をベン図で考えてみましょう。

ベン図

結婚したのか、かつ 結婚してないのか、
俺と…かつ 俺以外のヤツと…

結婚したのか、もしくは 結婚してないのか、
俺と…もしくは 俺以外のヤツと…

結婚したのか、

結婚してないのか、

俺と…

俺以外のヤツと…

結婚したのか、俺以外のヤツと…

結婚したのか、俺と…

結婚してないのか、俺と…

結婚してないのか、俺以外のヤツと…

結婚したのか、もしくは 俺と…

結婚してないのか、もしくは 俺と…

結婚したのか、もしくは 俺以外のヤツと…

結婚してないのか、もしくは 俺以外のヤツと…

結婚したのか、俺と…もしくは、結婚してないのか、俺以外のヤツと…

RTTIとRTTTの違い

はじめに

RTTIとRTTTって似てますよね
どっちがどっちか、わからなくならないように
改めて整理しましょう。

RTTI

実行時型情報 Run-Time Type Informationの略
C++では実行時にオブジェクトの型情報を持っておくことで、dynamic_casttypeidの使用が可能になります。

参考

ja.wikibooks.org

en.wikipedia.org

RTTT

ウマ娘 プリティーダービー ROAD TO THE TOP の略

2023年春にYouTubeで配信された、ナリタトップロードを主人公にしたウマ娘のアニメ。

参考

umamusume.jp

Switch2 当落シミュレーター

Switch2全然当たらんから
運を極めるために当落シミュレーターを作りました。

当たるか、当たらないかの2択しかないんだから、50%だよね??

実装

import std;

int main()
{
    std::uint32_t seed = 2386730010; // 乱数シード大事

    std::mt19937 rng(seed);
    std::bernoulli_distribution dist;

    for (int i = 0; i < 20; ++i) 
    {
        bool result = dist(rng);
        std::println("第{}回 Switch2 当落発表 {}", i + 1, result ? "当選" : "落選");
        if (result) {
            break;
        }
    }

    return 0;
}

シード値も決めて!いざ勝負!

神になれば運も操作できる

神になれば、乱数くらい操作できます C++では乱数エンジンを自作できます

/// <summary>
/// ✨ 神 乱数エンジン ✨
/// </summary>
class GodEngine
{
public:
    using result_type = std::uint32_t;

    GodEngine(result_type seed)
    {}

    result_type operator()() {
        return m_godState;
    }

    static constexpr result_type min() { return std::numeric_limits<result_type>::min(); }
    static constexpr result_type max() { return std::numeric_limits<result_type>::max(); }

private:
    constexpr static result_type m_godState = ~0; // 神のお導きによる値
};

int main()
{
    std::uint32_t seed = 2386730010; // 乱数シード大事

    GodEngine rng(seed);
    std::bernoulli_distribution dist;

    for (int i = 0; i < 10000; ++i) 
    {
        bool result = dist(rng);
        std::println("第{}回 Switch2 当落発表 {}", i + 1, result ? "当選" : "落選");
        if (result) {
            break;
        }
    }

    return 0;
}

いざ再挑戦!!

微分を使って毎フレーム乗算を加算にしてみる

はじめに

ゲーム制作ではフレームレートを考慮してdeltaTimeを使った計算をすることが多いです。

なんとなく毎フレーム乗算処理も、微分すれば毎フレーム加算に変更できるよね?っていうのを確かめたくなって
実験してました。
今回はその備忘録になります。

ところで、なんでもdeltaTimeを掛ければ良いわけではないことは
わかっていますでしょうか?

毎フレーム加算

ゲームループで足し算する場合、deltaTimeを考慮しないとFPSに依存して増加速度が変わってしまいます。
こういったものにはdeltaTimeを掛ければよいのでした。

before

y += x;

after

y += x * dt;

毎フレーム乗算

では、毎フレーム乗算する場合はどうでしょう?

before

y *= x;

これは、deltaTimeをかけるのではなく指数にする必要があります

after

y *= std::pow(x, dt);

これは難しい話ではなく落ち着いて考えればそうなることが良くわかると思います。
1秒たったら x倍
2秒たったら x倍のx倍
3秒たったら x倍のx倍のx倍

なので、指数の形になります。

微分とゲームループ

dy/dtが瞬間的な傾きなのでdtが小さい時
おおよそそのフレームの変化量とみなすことはできそう

y += dydt * dt;

フレーム乗算を微分で考える

さて、フレーム乗算も微分してdy/dtをだせば、加算処理に変換できそうです。

まずフレーム乗算を一般化しましょう。

 \displaystyle y = y_{0} \cdot x^{t}

これを微分すると以下のような式 / 微分方程式に変換できます。

 \displaystyle
\begin{align}
\frac{dy}{dt} &= y_{0} \cdot x^{t} \cdot log(x)\\
          &= y \cdot log(x)\\
\end{align}
y += y * std::log(x) * dt;

誤差が出る

実際に上記で実行してみると、結構誤差がでて、精度が悪い
deltaTimeの間隔内では、変化率が一定とみなして足しこんでいるので、
さらに細かく見たときの変化量が考慮できてない。  

その辺のよい補正処理がないか調べていた

Runge-Kutta法(RK4)

double k1 = f(t, y);
double k2 = f(t + 0.5 * dt, y + 0.5 * k1 * dt);
double k3 = f(t + 0.5 * dt, y + 0.5 * k2 * dt);
double k4 = f(t + dt, y + k3 * dt);
y += dt * (k1 + 2 * k2 + 2 * k3 + k4) / 6.0;

Runge-Kutta法というのがあるっぽく、それを今回のケースに適用した。

double k1 = y * logx;
double k2 = (y + 0.5 * k1 * dt) * logx;
double k3 = (y + 0.5 * k2 * dt) * logx;
double k4 = (y + k3 * dt) * logx;

y += dt * (k1 + 2 * k2 + 2 * k3 + k4) / 6.0;

まとめ

毎フレーム乗算も微分すれば加算系にできる。
誤差がかなりでるので、Runge-Kutta法などで補正すると、だいたい精度が改善する

ちなみに

パフォーマンス的には、普通にpowでやるほうが早いので、
加算にするメリットは特にない。

おまけ

微積とゲームループの話は以前にも記事を書いています。

qiita.com