CORDEA blog

Android applications engineer

【RoN BHS】set_object_health の挙動

Rise of Nations という Microsoft から発売された名作 RTS ゲームがあります。

このゲームには Big Huge Script という、ゲーム内でシナリオやルールを作るための言語があり、
今回はこの Script で使える関数の中から、 set_object_health という関数の挙動について説明します。

set_object_health とは

ゲーム内の Script Editor では次のように表示されます。

f:id:CORDEA:20190518122404j:plain

指定した ID の object の HP を 0-100 % の範囲で指定することができます。

tl;dr

  • 3 人 1 unit の場合、0-68% までは HP - math.floor(HP / 3)

挙動

この set_object_health は歩兵などの 3 人 1 unit の場合に不思議な挙動をします。
なお、ここでは Slingers を使用して確認しています。

どういうことかというと、単純に % 指定してそのとおりに動くとは限らないということです。

まず、ターゲットの HP を 0 に設定する場合を考えてみます。

set_object_health(who, id, 0);

これを騎兵に適用すると正常に行われます。
これは想定通りの挙動と言えます。

では、歩兵に適用してみます。

f:id:CORDEA:20190518122433j:plain

これは 3 人で 1 unit という扱いであるものの set_object_health は 1 人に対して適用される場合がある、ということの気がしています。
様々な数値で試してみると判るのですが、0-68% までは HP は 57 です。必ずしも指定する値と HP は一致しません。
なぜ 57 なのかは、1 人減っていることから

85 - (85 / 3) = 57

と考えられます。(より正確には 85.0 - math.floor(85.0 / 3.0))

さて、先程 1 人に対して適用される場合がありそうと書きましたが、では 80% など 69% 以上を設定する場合を見てみます。

set_object_health(who, id, 80);

この結果は以下のようになります。

すなわち、

85 * 0.8 = 68

で、1 人ではなく、1 unit に対して適用されていそうなことがわかります。
以上のように、歩兵などの unit に適用する場合は指定 % に対して HP が比例しないため、注意が必要です。

確認に使用した Script は以下にあります。

RoN_rules/Example at master · CORDEA/RoN_rules · GitHub

Chrome のコンテキストメニューから印刷...を消す

定期的に検索と印刷...を押し間違えて辛いので消したメモ。
もしかしたら Chrome の設定とかでできるかもしれないので、直接書き換えたくない人は探してみてください。

やり方については StackExchange に載っている通りで、Preferences の printing.enabled を false にすれば良いという話。

superuser.com

この Preferences は macOS の場合 ~/Library/Application Support/Google/Chrome/Default 配下にあります。 (Windows の場合の PATH は先のリンクに書いてありました)
printer とかがある人はすでに "printing" があると思うので、そこに "enabled" を書き加えると良いです。

Before

f:id:CORDEA:20190414121735p:plain

After

f:id:CORDEA:20190414121757p:plain

backup を用意した後 jq で format して書き換えて入れておいたのですが、起動時に勝手に minify してくれました。

おわり

Thread を toString した時の Thread[foo,0,bar]

RxJava とか使ってると稀によく見る Thread[main,5,main] みたいなやつ、どれがどれだっけってなったのでメモ

そもそも RxJava の source から読み始めたのでアレなんですが、
タイトルに書いてあるように Thread の doc か toString の実装を見ればいいです
Thread (Java Platform SE 8)
jdk8/jdk8/jdk: 687fd7c7986d src/share/classes/java/lang/Thread.java

"Thread[" + スレッド名 + "," + 優先順位 + "," + スレッドグループ + "]"

RxJava でよく見る RxCachedThreadScheduler-0 はここです

RxJava/IoScheduler.java at v2.2.8 · ReactiveX/RxJava · GitHub

後ろの番号を set しているのは

RxJava/RxThreadFactory.java at v2.2.8 · ReactiveX/RxJava · GitHub

おわり

static method 等を呼び出しているテストケースで Robolectric の Shadow を使う

あんまり知られてないような知られてるような、そんな感じがしたので Robolectric の Shadow の使い道をちょっと紹介します。

robolectric.org

紹介するのは

  • static method を呼び出ている
  • kotlin の object 宣言がされた singleton を内部で直接使用している
  • 内部で new している

などなど、色々な記事で PowerMock の出番とされていそうなケースについてです。

今回はこれを Robolectric の Shadow で解決します。
前提として、私は PowerMock を入れようと思った時、それは設計を見直しをするべき時だと考えています。
とはいえ、古いアプリや大規模なアプリなどでテストは書きたいが static method の呼び出しがネックになっている、直す工数もない。しかし PowerMock は入れたくないというケースは稀ですが存在します。
そんな時、使うべきかは置いておいて Shadow が使えます。

いくつか例を紹介します。
この例は全て GitHub にあります (難読化された感じの雑な命名はあとで直します)
github.com

1 つ目

object AUtil {
    fun a() {
        throw IllegalArgumentException()
    }

    @JvmStatic
    fun b() {
        throw IllegalArgumentException()
    }
}

class A {
    fun a() {
        AUtil.a()
    }

    fun b() {
        AUtil.b()
    }
}

1 つ目の例はこれです。
こんなの存在しないと思いますが、テストからアクセスできない何かにアクセスして throw されてる、でもその後の処理がテストしたい...!みたいな、そんな感じで考えてください。

これに対しては、以下のように Shadow を定義できます。

@Implements(AUtil::class)
class ShadowAUtil {
    companion object {
        @JvmStatic
        @Implementation
        fun b() {
        }
    }

    @Implementation
    fun a() {
    }
}

これを使用するとテストを通過させることができます。

@RunWith(AndroidJUnit4::class)
@Config(shadows = [ATest.ShadowAUtil::class])
class ATest {
    @Test
    fun a() {
        A().a()
    }

    @Test
    fun b() {
        A().b()
    }

    ...
}

2 つ目

2 つ目は内部で new していて、なんか失敗しているケースです。
Shadow は constructor の呼び出しにも対応しています。

class D(d: String) {
    init {
        throw IllegalArgumentException()
    }
}

以下のような Shadow を定義します

@Implements(D::class)
class ShadowD {
    @Implementation
    fun __constructor__(d: String) {
    }
}

これを使用すると通過します
また、これは変な使い方として、

class B(private val b: String)

このようなクラスを Java 側から使用するとして

public class C {
    public void c() {
        new B(null);
    }
}

null を入れてしまうと当たり前ですが java.lang.IllegalArgumentException: Parameter specified as non-null is null ... が発生します
が、以下のような Shadow を定義すると回避できます。

@Implements(B::class)
class ShadowB {
    @Implementation
    fun __constructor__(b: String?) {
    }
}

使い所はありません。

3 つ目

実際に呼び出されたかどうか知りたい時、引数を assert したいときなど。ここまで来たら PowerMock 使えばという話なんですが、可能です。

@Implements(AUtil::class)
class ShadowAUtil {
    var isPassed = false

    @Implementation
    fun a() {
        isPassed = true
    }
}

とりあえずこんな感じで定義しました。
Robolectric は object に対応する Shadow を受け取ることができます。

@Test
@Config(shadows = [ShadowAUtil::class])
fun a() {
    val util = Shadow.extract<ShadowAUtil>(AUtil)
    assert(!util.isPassed)
    A().a()
    assert(util.isPassed)
}

便利です。

Shadow は多くの場面で活躍しますが、一方で何をしているのかが慣れるまで分かりづらいという欠点がありますので、用法用量を守って使用すると良さそうです。

Mackerel Client 1.2 をリリースしました

この度、MackerelClient 1.2 をリリースしましたので、リリースノートに書かなかった諸々を書きます。
ちなみに 1.1 (2016年4月24日) 以来、2 年半ぶりの更新となります。

Get it on Google Play

反映されるのはもう少し後かもしれません
スクリーンショット等はそのうち更新します

github.com

リリース内容

  • アラート一覧にホスト名や監視設定の値を表示するようにした
  • アラートの取得、グラフの描画を早くした
  • デザイン修正
    • card 部分のスタイルをちょっと変えた
    • グラフ部分のスタイルをちょっと変えた
  • バグ修正
    • ドロワーの文字色がいつの間にか毒々しくなってたので直した (というか直った)
  • min sdk version: 19 -> 21
  • target sdk version 23 -> 28

内部的な変更

  • 264 files changed, 8403 insertions(+), 6158 deletions(-)
  • ktlint の導入
  • dagger の導入
  • databinding の導入
  • groupie の導入
  • parcelize の導入
  • AndroidX 対応
  • 諸々のバージョンアップ
    • kotlin 1.0.1 -> 1.2.71
    • realm 0.87.2 -> 5.1.0

大規模な設計の見直しが入っており、差分がかなり大きくなりました。
グラフの描画部分など、主機能については大きく書き直しています。
まだ完全ではありませんが、まぁまぁまともになってきています。


気が向いたら使ってみてください。

Hy と Python の version について

Hy の install 時に Python version で少し困ったりしたのでメモ

普通に Python 3.7.0 の環境で Hy を pip install して実行すると

ImportError: invalid flags 1531398560 in 'hy.core.language'

こうなったり、または一回は実行できるけど二度目は失敗する、みたいなことになる

Hy 0.14.0 は Python 3.7.0 に対応していないためにこのエラーは起きているようで
これより後に 3.7.0 対応が行われている

と言うわけで対応としては公式通り、基本的には

$ pip install hy

よりも

$ pip install git+https://github.com/hylang/hy.git

の方が良さげ

Spacemacs の install で嵌ったりした

最近 Lisp 方言を書く機会が多いので重い腰を上げて Emacs を入れました
といっても素の Emacs を育てるには Vim に染まりすぎたので
Spacemacs という Emacs の distribution に頼ることにしました

嵌ったところ

公式にしたがって ~/.emacs.d に clone したのですが

Error (use-package): org-projectile :config: Symbol's function definition is void "projectile-global-mode"

的なことを言われたり言われなかったり、mode がうまく切り替わらなかったりして嵌ってました

で、spacemacs 自体を develop に切り替えることでうまくいきました
予想ですが、この PR が関係してそうだなと

$ cd ~/.emacs.d
$ git checkout develop

このあと一応 dotspacemacs/install し直してます

その他

思ったより Spacemacs 良かったです
C-x C-c と C-g だけ覚えとけばなんとかなる