Blog

オープンなかな漢字変換エンジン 0.2 リリースのお知らせ

オープンなかな漢字変換エンジンを開発しているということを先日お知らせしました。 https://blog.64p.org/entry/2023/01/16/032912

そのあと、順調に開発が推移しまして、いい感じにいろいろな機能が盛り込まれました。日本のOSSでは29日に定期リリースするという監修があります(最近聞きませんが。。)なので、今日 v0.2.0 を出しました。出したと言っても tag を打っただけですが。

https://github.com/akaza-im/akaza/releases/tag/v0.2.0

など、利用するにあたって基本的な機能を一通り実装した感じになります(ほかにもいろいろあるのだけど、おおすぎて書ききれない)。

特に大きな変化として、外部辞書を利用できるようになったことがあります。SKK 形式の外部辞書が使えるようになったので、何らかの新しい語彙が必要になった時は、SKK形式の辞書を利用させてもらえばよいのです。圧倒的な安心感ですね〜。

多分、割とインストールして試せるぐらいにはなってきたかなーと思います。ので、試してみていただけるとありがたいです。

本バージョンを出すまでに omasanori さん、tkng さん、pklionさんにご協力いただきました。ありがとうございました!

Rust で整数を漢数字に変換したいよー

こんな感じで実装できる。真面目にやればもうちょいちゃんと書けるけどこんな感じで。。

use std::cmp::min;

const NUMS: [&str; 10] = ["", "一", "二", "三", "四", "五", "六", "七", "八", "九"];
const SUBS: [&str; 4] = ["", "十", "百", "千"];
const PARTS: [&str; 18] = [
    "",
    "万",
    "億",
    "兆",
    "京",
    "垓",
    "𥝱",
    "穣",
    "溝",
    "澗",
    "正",
    "載",
    "極",
    "恒河沙",
    "阿僧祇",
    "那由他",
    "不可思議",
    "無量大数",
];

fn int2kanji(i: i64) -> String {
    let s = i.to_string();
    let chars = s.bytes();
    let p = chars.into_iter().rev().enumerate().collect::<Vec<_>>();
    let mut buf: Vec<&'static str> = Vec::new();
    for (i, b) in p.clone() {
        let c = (b - 48) as usize; // 48 is '0'
        if i % 4 == 0
            && i > 0
            && (i..min(i + 4, s.len()))
                .map(|i| {
                    let (_, c) = p.get(i).unwrap();
                    *c
                })
                .any(|n| n != 48)
        {
            buf.push(PARTS[i / 4]);
        }
        if c != 0 {
            // その桁が 0 のときは区切りを追加しない
            buf.push(SUBS[i % 4]);
        }
        if !(i % 4 != 0 && c == 1) {
            // 十百千を表示したときで、一のときは追加しない。
            buf.push(NUMS[c]); // 48 is '0'
        }
    }
    buf.reverse();
    buf.join("")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_int2kanji() {
        assert_eq!(int2kanji(1), "一");
        assert_eq!(int2kanji(9), "九");
        assert_eq!(int2kanji(10), "十");
        assert_eq!(int2kanji(11), "十一");
        assert_eq!(int2kanji(21), "二十一");
        assert_eq!(int2kanji(99), "九十九");
        assert_eq!(int2kanji(100), "百");
        assert_eq!(int2kanji(999), "九百九十九");
        assert_eq!(int2kanji(1000), "千");
        assert_eq!(int2kanji(9999), "九千九百九十九");
        assert_eq!(int2kanji(10000), "一万");
        assert_eq!(int2kanji(10020), "一万二十");
        assert_eq!(int2kanji(1_000_020), "百万二十");
        assert_eq!(int2kanji(100_000_020), "一億二十");
        assert_eq!(int2kanji(1_0000_4423), "一億四千四百二十三");
        assert_eq!(int2kanji(1_8000_4423), "一億八千万四千四百二十三");
    }
}

最近またLinux用の日本語IMEを作っている

本件は mozc の ut がどうこうとかは関係なくて、ふと linux desktop を使おうと昨年末に思いまして、昨年末からちまちまやってます

https://github.com/tokuhirom/akaza

かな漢字変換って作るの難しいのかなぁ、と思ったので作ってみている。これはまさに Just for Fun でやっている。 わりと普通に自分で常用してる分には困らないかな、というところまできている。

以下は、思ってることの垂れ流しという感じで、まとまってないですが。

「日本語入力を支える技術」という本が 2018年に出ていて、この本の内容を読めば、だいたいエンジン部分は実装できる。Amazon のレビューではこの本よんでも実装できないって書いてあるけど、変換エンジン自体は実装できます。 UI が辛い。けど。

エンジンは、ビタビアルゴリズムで最小コスト法を実装する、とかであればプログラミングできる人なら割とまじで誰でも実装できる。

ただ、どうしてもかな漢字変換エンジンは大量の細かいオブジェクトを取り扱う必要が出てくるので、python とかで実装すると結構遅い。 2年前に一回 pure python で実装したが、おそすぎたのでコア部分を C++ で書き直したという経緯がある。 Python で書いてると、色々なアルゴリズムためす上でも、Python が遅いからだめなのか、アルゴリズムがそもそも遅くて無理なのかがよくわからなくなりがちなんだよな。

UI を Python で書いて、ロジックを C++ で実装したのだがどうもインストールまわりが煩雑になるし、Rust 触ってみたかったので rust で全面的に書き直した。 Pure Rust にしたことによって、SEGV したりして謎に死ぬことがなくなったので快適になったし、高速に動いている。所有権とかがまぁめんどくさいとかはありつつ、わりと慣れれば大丈夫になる。

基本的な方針としては、mozc に近い感じの構成になっていて、最小コスト法でビタビアルゴリズムで統計的かな漢字変換である。 個人で開発しているから Google と違って大量のウェブコーパスを利用できるわけではないから、Wikipedia と青空文庫を形態素解析してそれを学習元として利用している。 mozc は Google の大量の資源を利用できるから変換精度がいいんでしょ、と思う人が多いと思うんだけど、それはまったくもってそのとおり。ただ、書き言葉という点においては、wikipedia ぐらいの規模の言語資源があれば、それを使えば割とそこそこの変換精度は出る。逆に話し言葉はめちゃくちゃ弱いので、話し言葉とかくだけた書き方のコーパスがもっとほしい。 利用できる日本語のコーパスとしては BCCWJ がほぼ唯一に近いもので、BCCWJ を使えばもっと変換精度は向上するんだけど、、個人で利用しようとすると 25万円だかかかるので二の足を踏んでいる。

識別モデルを使う というのも試してみた。今どきだと機械学習的なアプローチがナウいかなぁと思い。。だが、どうしても個人のマシンで動かしていると時間がかかってしょうがない。金をかけられない個人の趣味開発においては、統計的かな漢字変換のほうが向いていると思う。 開発を継続的にやるには無料の環境で使えるのが大事かなと思っている。モデルデータを github actions とかで生成できるのが理想的で、github actions で生成するならチープなスペックのインスタンスで 回せるぐらいのものが良い。github actions は 6 時間しか回せない。

基本的に今は単語 Bi-gram でコスト計算している。tri-gram に拡張するとプログラムが複雑になりすぎるし、有効なケースも限定的なのでまぁ。あまり bi-gram で困ってないし、という。mozc のようにクラスNグラムにしたほうがいいのかなぁ、と思いつつ、単語Nグラムで困ってないのでまぁいいかなぁといったところ。単語Nグラムだと品詞のこととか考えなくていいのがとにかく良い。 日本語の品詞難しいよ〜。個人的には、かな漢字変換の辞書登録で品詞を入れさせられるの、だるいなと前々から思ってたし。

https://komachi.hatenablog.com/entry/20081127/p2 http://www.fenix.ne.jp/~G-HAL/soft/nosettle/anthy.html#patch13

↑このへんとかを見ても思うこと。個人的には SKK の学習の具合を気に入っている。システムで提供するモデルはもちろん大事なのだが、個人の語彙などはたかが知れているので、システムで提供するモデルは「ある程度学習が進むまで耐えられる」ぐらいの精度があればよく、細かいところは個人の学習データで補ってくれや。っていうふうにするしかないのかな、と思っている。 だって個人だと Google みたいな規模のコーパス使えないんだもの。

ユーザーの学習は、unigram と bigram をテキストファイルで ~/.local/share/ とかに落とすようになっていて、自分が変換した結果の確率が学習データにあればそっちが優先されるようになっている。ので、誤変換で確定しちゃっても、正しい変換で何回か確定すればそれが優先されるようになる。このへんの学習ロジックはめちゃくちゃシンプル。基本的にユーザーの語彙は wikipedia 全体の語彙よりも少ないから、分母が小さくなるので、ユーザーの学習結果の確率計算のほうがキツく効くっていう仕組みにしている。

ちなみに、この手のかな漢字変換エンジンって、どういうふうに考えて実装したか、みたいな日記を書いてる人が多くてそういうの読むのって面白いなぁと思う。僕はそのへんの経緯を github issues とかに書くのがいいのかなぁと思ったので github issues とか discussions とか wiki に書いている。バザール的に開発できるような感じの構成にしているつもり、ではある。 お気軽になにかアイデアとかあれば書いていただければ嬉しいし、PR 送っていただければなお嬉しい。

https://github.com/tokuhirom/akaza/discussions

今月中ぐらいに一旦スナップショットリリースを出来たらいいなぁと思っている。

lindera-cli を試す

Rust で書かれた形態素解析機の lindera には CLI もある。

cargo install lindera-cli --features ipadic

のようにして入れる。辞書がオプショナルなので、features オプションを指定しないで入れると何もできない。ipadic か unidic を指定すること。

実行は以下のようになる。

echo "やはり原因はヤッポ" | lindera tokenize -t ipadic
やはり  副詞,一般,*,*,*,*,やはり,ヤハリ,ヤハリ
原因    名詞,一般,*,*,*,*,原因,ゲンイン,ゲンイン
は      助詞,係助詞,*,*,*,*,は,ハ,ワ
ヤッポ  UNK
EOS

cargo test で fork させたい

https://crates.io/crates/rusty-fork

cargo test はプロセスを共有する。これではテストをしづらいケースがある。例えば C のライブラリのラッパーなどの場合、初期化関数を何回も呼んではいけないケースがある。

rusty-fork crate を利用すると、以下のように書くだけで別プロセスでテストを実行させることが可能である。

use rusty_fork::rusty_fork_test;

rusty_fork_test! {
    #[test]
    fn my_test() {
        assert_eq!(2, 1 + 1);
    }

    // more tests...
}

rust の split_once が便利だった。

最近、趣味で僕が書いてるプログラムでは split を 2 こまでしたいというケースが多くて、以下のように書いていた。

fn main() {
    let s = "foo bar";
    let p :Vec<&str> = s.splitn(2, ' ').collect();
    println!("{},{}", p[0], p[1]);
}

これは、実は split_once というメソッドを使えば良い。

fn main() {
    let s = "foo bar";
    let (a, b) = s.split_once(' ').unwrap();
    println!("{},{}", a, b);
}

便利すぎる。

Rust の FFI で C の callback に closure を渡したい

https://adventures.michaelfbryan.com/posts/rust-closures-in-ffi/

↑の記事で丁寧に解説されているので、読むと良い。

ポイントとしては、rust は extern "C" された静的な関数しか FFI 対象に渡すことができないということ。そして、closure を void* に直接キャストすることもできないので、トランポリン使ってやりましょうね。ということです。

rust で `*mut u8` から `&[u8]` を求める

FFI などで *mut u8 な pointer と i32 な size が得られたときに、そこから &[u8] というスライスにしないと rust 側では扱いづらい。 そういう場合には std::slice::from_raw_parts(image, size as usize); を利用する。

https://stackoverflow.com/questions/50941427/casting-mut-u8-to-u8-without-std

jumpapp を i3 に適用して一発でアプリにフォーカスをあてる on Linux

jumpapp というコマンドがある。これを使うと該当のウィンドウが起動していなければ起動し、起動していればフォアグラウンドにもってくることができる。

僕の場合は i3wm を使っているので、.config/i3/config に以下のように設定する。

bindsym $mod+Ctrl+Shift+o exec "jumpapp obsidian"
bindsym $mod+Ctrl+Shift+c exec "jumpapp -c google-chrome google-chrome-stable"
bindsym $mod+Ctrl+Shift+t exec "jumpapp moderndeck"
bindsym $mod+Ctrl+Shift+w exec "jumpapp wezterm"
bindsym $mod+Ctrl+Shift+f exec "jumpapp -c franz franz"

Ultimate hacking keyboard のバンパーを Ctrl+Shift+Super に割り当てて、2つのキーを押すだけでアプリを起動できるようになった。便利。

linux desktop で slack app がうまくログインできないときは frantz をためそう

linux desktop で slack app を使おうとしてもログイン後に browser から戻ってくるところでなんかうまくいかなくてログインできないことがある。 そういう場合は、Frantz を使うといいかも。

frantz は slack や gitter などの web chat をまとめて一つの画面で表示してくれるという便利アプリ。 コレ自体便利なので昔は使っていたが最近つかってなかった。

slack のアプリと違って frantz はログインフローを frantz アプリ内の埋め込みブラウザで処理するので、slack app のような問題はおきない。

本質的な解決策ではないが、個人マシンである desktop linux でログインするのは雑談チャットとかOSS関連のチャットだけなので、まぁ frantz でいいかなぁ、といったところ。

ライフゲエムを実装した

ライフゲエム、たぶん過去に何回か作ろうと思ったことがある気がするんだが、自分が実装したものが github に見当たらなかったのでさっとつくった。もしかしたら過去にも作ったかもしれないが記憶にございません。

時間計測してないけど2時間ぐらいでできた。

https://tokuhirom.github.io/lifegame/index.html https://github.com/tokuhirom/lifegame

react とか使ってもいいけど、こんなもんは pure javascript で書いたほうがいいだろうということで、pure javascript です。

↓このサイトを参考に実装しました(JavaScript コードは見てない)。 http://math.shinshu-u.ac.jp/~hanaki/lifegame/

arch linux で使うのに最適な twitter クライアントを探す

最近色々ある twitter ですが、なんだかんだ twitter クライアントを起動しておきたいなと思っている今日この頃なので、、

最初は CawBird を使っていたが、vim keybinding が使えなかったりしてちょっと不満。

なので、気合で自分にあうものを探してみようかなと。yay -Ss twitter として twitter 関連のものを探し、その中から GUI クライアントを絞り込むものとする。このキーワード検索では、どうしてもライブラリや CLI ツールなどが混ざってくるので、目で確認していく。

aur/birdie-git 1.1.r12.g20b3c83-1 (+0 0.00) (Orphaned)
    Twitter client for Linux
aur/v2cmod-z 2.19.1-8 (+0 0.00)
    A 2ch/5ch and Twitter browser
aur/moderndeck-bin 9.4.5-1 (+1 0.24)
    A beautiful, powerful Twitter client for desktop.
aur/mikutter-git 3.4.2.r0.gdf0a70c-1 (+2 0.00)
    a moest twitter client (Upstream version)
aur/alice-git latest-1 (+2 0.00)
    A minimal Twitter client based on GTK+3.
aur/nocturn 1.8.4-2 (+6 0.00)
    Desktop twitter client
aur/cawbird-git 1.5.r0.g0a60a0bc38-1 (+7 0.01)
    A fork of the Corebird GTK Twitter client that continues to work with Twitter
aur/choqok-git v1.6.0.r106.gd8a53169-1 (+35 0.00)
    Microblogging client for KDE with support for Pump.io, GNU social and Twitter.com - git version
aur/mikutter 5.0.4-1 (+39 0.00)
    a moest twitter client
community/cawbird 1.5-2 (644.4 KiB 2.0 MiB) (Installed)
    Native GTK Twitter client

cawbird

cawbird が微妙なのは、hjkl 移動できないという点とフォントがなんか汚い。 フォントは設定すればなんとかなりそうだが。。

cawbird で issue たっているが、あんまメンテナが乗り気じゃなさそうだった>ショートカット

birdie

インストール不能

nocturn

すでに試したのだが起動しなかった。

[1022523:1222/153244.185725:FATAL:gpu_data_manager_impl_private.cc(439)] GPU process isn't usable. Goodbye.
zsh: trace trap (core dumped)  nocturn

と言われてしまう。

これは nocturn --no-sandbox すれば起動はできる。

が、結局なんかしらんけどログインができない。

mikutter

twitter client ではなく mastdon クライアントになっていた

v2cmod-z

2ch クライアントがメインっぽい。。

alice-git

なんか github レポジトリが消えてる

choqok

動く。動くのがすごい、みたいになってるのがちょっとあれだけど。。 choqok は一番まともかな。ただ、twitter 特化じゃないのでなんか変。 fav するためのショートカットがなかったり、vim like keybindings がなかったりする。

これだったら cawbird でいいかな。

moderndeck

これが、基本的には twitterdeck なので体験が良い。

ということで、moderndeck を使うことにした。 Use os native title bar を appearance 設定で利用することで、ちゃんと i3 で枠がつくようになる。

というわけで、しばらくは moderndeck を使おうかなと思う。

Obsidian を linux で使うとウィンドウに枠がつかないとき(i3 などの場合)

linux の window manager はウィンドウに対して枠を付与するものが多いのだが、Obsidian はデフォルトでは Window manager のウィンドウの枠を無効化する設定になっている。 しかしそのままではアクティブなウィンドウがどれかわかりにくいので、枠を付与したい。

調べてみると、Appearance の設定のなかに Window frame type というのがあるので、これを Native frame に変更すれば良い。

i3wm でスクショを取る設定

https://dev.to/dianjuar/i3wm-screenshot-shortcuts-3n7b

を参考に設定。

PrtScn で選択した領域をスクショできるだけでまぁいいかなという気がしたので以下の設定だけ入れました。

bindsym Print exec --no-startup-id maim --select | xclip -selection clipboard -t image/png↲

xremap を使い始めた

前回 linux desktop 環境を構築したときには、xkeysnail を linux 用のキーリマッパとして利用していたのだが、いつのまにか xremap が rust で書き直されていたので xremap に移行することにした。 yaml で設定できるのも良い。

基本方針として、Mac のような 2 つのモディファイアをを駆使したキーマッピングにしたい。 つまり、ctrl+n, ctrl+f などの emacs binding と、Cmd+w, Cmd+f などのメニュー操作の両立ができるという Mac みたいな入力スタイルを実現したい。 Mac でこれになれてしまったので、このスタイルが楽なのである。

幸い、Ultimate hacking keyboard を使っているのでかなり自由にキーマッピングを変更可能なので、Ultimate hacking keyboard 側のマッピングをいじりつつ xremap でもマッピングしていい感じに変更していく。

とはいえやっていることはシンプルで、Mac でいう cmd の位置を alt にして、a のとなりは ctrl とするというだけ。

あとは、terminal 上以外では Emacs like なキーバインディングを設定するというのと、chrome は win/linux と mac でキーバインディングに差異があるので、mac のキーバインディングで操作できるように調整する。最後に Alt+〇〇 を Ctrl+〇〇にリマップすれば完成。割とシンプルな構成で実現できた。xremap 便利。

# Mac でいう Cmd の位置に alt key をアサインしている。
# ctrl キーは小指の位置。
keymap:
  - name: Emacs
    application:
      not: wezterm
    remap:
      C-b: { with_mark: left }
      C-f: { with_mark: right }
      C-p: { with_mark: up }
      C-n: { with_mark: down }

      C-a: { with_mark: home }
      C-e: { with_mark: end }

      C-h: { with_mark: backspace }

      C-m: enter
      C-j: enter

      C-d: [delete, { set_mark: false }]

      C-k: [Shift-end, C-x, { set_mark: false }]
      C-leftbrace: esc


  # Chrome tab switching
  - name: Chrome
    application:
      only: Google-chrome
    remap:
      Shift-Alt-rightbrace: C-tab
      Shift-Alt-leftbrace: Shift-C-tab
      Alt-leftbrace: Alt-Left

  # remap alt-key to control
  - name: Cmd key mapping
    remap:
      Alt-a: C-a
      Alt-b: C-b
      Alt-c: C-c
      Alt-d: C-d
      Alt-e: C-e
      Alt-f: C-f
      Alt-g: C-g
      Alt-h: C-h
      Alt-i: C-i
      Alt-j: C-j
      Alt-k: C-k
      Alt-l: C-l
      Alt-m: C-m
      Alt-n: C-n
      Alt-o: C-o
      Alt-p: C-p
      Alt-q: C-q
      Alt-r: C-r
      Alt-s: C-s
      Alt-t: C-t
      Alt-u: C-u
      Alt-v: C-v
      Alt-w: C-w
      Alt-x: C-x
      Alt-y: C-y
      Alt-z: C-z

  # find the app name by `wmctrl -x -l` on X

archlinux で fcitx5 で特定のアプリ(chromeなど)で日本語入力できないとき

pacman -S fcitx5-im

とする。fcitx5-gtk や fcitx5-qt などがインストールされて問題が解決する。

arch linux を入れる 2022年版

久々に arch linux を使おうかな、と思いまして。なんかもうプライベートで使うマシン、mac じゃなくていいなーって気分が高まりすぎてる。 archlinux の利点は、pacman で管理できて、だいたいのものが AUR 経由でインストールできるので便利という感じなのと、ubuntu と違って、必要なものしか入らない感じにできるってことかな。 (その結果として、必要なものが入ってなくて変な状況になることもあるけど)

昔は頑張って windows のディスクをパーティションきって設定したりしてたけど、めんどくさいので SSD を USB で増設してやる。最近は USB 接続でも十分な速度が出るので良い時代だ。

https://wiki.archlinux.jp/index.php/USB_%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%83%A1%E3%83%87%E3%82%A3%E3%82%A2 rufus で usb memory に書いた。

あとは、公式のガイド通りに設定していけばいい。fdisk はちょっとだるいので、TUI でできる cfdisk でやった。 https://wiki.archlinux.org/title/installation_guide

細かいところでよくわかんなかったところは https://zenn.dev/ytjvdcm/articles/0efb9112468de3 を参考にしたのだが、何を参考にしたのか忘れた。たぶん EFI 周り。

そして、i3wm を startx するような構成にした。 具体的に、入れてるのは以下のようなものです。

#!/bin/bash

set -ex

# install: pacman -S pkgname
# search:  pacman -Ss keyword

# update list
sudo pacman -Syu

# core
sudo pacman -S --needed --noconfirm openssh vim git tmux sudo git-lfs gcc base-devel go the_silver_searcher zsh w3m curl

# nvidia driver(if needed)
sudo pacman -S --needed --noconfirm nvidia

# manage mirror site
sudo pacman -S reflector
sudo reflector --country 'Japan' --age 24 --protocol https --sort rate --save /etc/pacman.d/mirrorlist

# network
# network-manager-applet=nm-applet
sudo pacman -S --needed --noconfirm networkmanager network-manager-applet
sudo systemctl enable NetworkManager

# yay
# yay is the wrapper for AUR and pacman.
if [[ -e /usr/bin/yay ]]; then
    echo "yay exists"
else
    git clone https://aur.archlinux.org/yay.git /tmp/yay && cd /tmp/yay && makepkg -si
fi


# X
sudo pacman -S --needed --noconfirm wezterm firefox i3lock xdg-utils
yay -S --needed --noconfirm google-chrome
yay -S --needed --noconfirm obsidian
# startx
yay -S --needed --noconfirm xorg-xinit

# i3
sudo pacman -S --needed --noconfirm rofi i3-wm polybar xss-lock dunst

# Japanese
sudo pacman -S --needed --noconfirm adobe-source-han-sans-jp-fonts adobe-source-han-serif-jp-fonts otf-ipafont noto-fonts noto-fonts-cjk noto-fonts-emoji
yay -S --needed --noconfirm ttf-mona ttf-monapo ttf-ipa-mona ttf-vlgothic ttf-mplus ttf-koruri ttf-mplus ttf-sazanami ttf-hanazono
sudo pacman -S --needed --noconfirm fcitx5-mozc fcitx5-configtool fitx5-gtk fcitx5-qt fcitx5-im

# JetBrains
yay -S --noconfirm intellij-idea-ultimate-edition intellij-idea-ultimate-edition-jre
yay -S --noconfirm jetbrains-fleet

# Ultimate hacking keyboard
yay -S --noconfirm uhk-agent-appimage

./setup-vimplug.sh

# I need to enable multilib for installing steam app
yay -S --noconfirm steam

# Chatting
yay -S --noconfirm slack-electron

Mount & Blade Ⅱ Bannerlord のキャンペーンをクリアした

シミュレーションゲーム。アクション要素もあるが、途中からめんどくさくてほぼアクションパート飛ばしていた。

序盤は盗賊を狩って、あとは鍛冶で金を稼いで、という感じ。 序盤に鍛冶が得意な人と医学が得意な英雄を集めて攻略。

帝国を滅ぼすところまでキャンペーンでやって満足。プレイ時間は128時間。

難易度は「こそ泥」。「戦士」にすると急に難しくなるなぁという印象。

今年やったゲームの中だと一番おもしろかったかもしれない。

Ultimate hacking keyboard を買った。これは Ultimate かも?

分離型のキーボードとして MD550 を使い始めた。分離型は方が自然な位置になるのでなかなか気に入りまして、Kinesis の freestyle2 などいくつか試しました(Kinesis は微妙に手になじまずすぐに諦めてしまった)。 そんな中、Ultimate hacking keyboard を知って購入したのが数ヶ月前。長らく立って、やっと届いたのが先週ぐらい、という感じ。

特徴としては

もともと値段が高いデバイスであり、さらに円安もあるのとハンガリーという遠い場所で作られてるということもあり、まぁ高い。 ポインティングデバイスがトラックボール、トラックポイント、タッチパッドから選べるのでそれを全種類うっかり頼んだのでそれもあってまぁ高くなった。

買ったもの

サイレントの茶軸。サイレントとはいえまぁそこそこ音はします。打鍵感は最高に良いです。

キーマッピング

キーマッピングはさすがに Ultimate というだけあって、複数をキーボード側に記憶させることができる。 なので、PC用とMac用で完全にワケたキーマッピングを設定している。

Windows 用のキーマッピング

Windows を操作するときに問題になるのが、Mac では Cmd と Ctrl という2つのモディファイアを使うことによって、編集系の操作とメニュー系の操作を行うことができるわけだが、これを行う機能が Windows にはないということだ。これは非常に面倒で、擬似的に Mac 風な操作を AutoHotKey で実現していた。 しかし、AutoHotKey の設定ファイルは記号まみれでいじるのがしんどい感じのファイルなので、あまりメンテナンスする気が起きないものなのである。

Ultimate hacking keyboard なら、そういった悩みはなく、GUIでポチポチ設定すればOK。簡単だった。 マクロも設定できるんで、案外複雑なマッピングもできる。

ベースマップ

ベースのキーマップはそこまでいじらず。

Mod マップ

デフォルトだとカーソルの移動が独特なので、HJKLスタイルに変更。音量の切り替えもここに入れた。音量の切り替え、どうしてもゲームやってるときとかによく使うので、割と触りやすい位置においておきたかった。

Mouse マップ

マウスキー機能がある。が、実際にはアタッチメントをつけてるのであんまいらないかな。と思いつつ一応設定。 HJKL 風にしている。

Fn マップ

基本的にはキーマップの切り替え機能だけを設定している。

Fn2 マップ(Cmd相当)

Cmd キーを押したときをエミュレートしている。

Fn3 マップ(Ctrl 相当)

Ctrl+A を押したときに Home キーを出す、みたいなのを設定している。 Ctrl+K で kill する部分は、マクロで設定している。

Mac 用のキーマップ

基本的には Windows 用と一緒。 ただし、いくつかのカスタマイズを入れている。

進歩するハードウェア

Ultimate hacking keyboard の良いところは、V2 になったことでかなり進化していて、かつファームウェア的にも進歩しているようだ。 初期の頃に Ultimate hacking keyboard を買った人のブログ記事を一通り読んだのだが、初期の頃に買った人が書いている改善点がかなり改善されてきていて、かなり良くなっているように思う(ありがとう先人の人たち)

特に大きいのがスマートマクロ機能で、起動時に設定を変えるとかそういうのができるようになっている。 個人的にはOSごとに違うキーマップを設定しているので、OSの切り替え時に自動的にキーマップを変えられる機能がほしい。今は UHK Agent を自前でビルドすればできるぽいけどまだ試していない。

まとめ

結論、とても満足している。ポインティングデバイスが一体化していることのメリットがとにかくでかいし、キーマッピングをめっちゃこまかく設定できるんで快適。 今までだったらできなかったような設定ができてる(多分似たようなことができるものは存在しているのかもしれないが、僕は他に知らない。) ファームウェアのアップデートなどでできることが増えているようだし、今後使い続けていくのも楽しみでならない。

kotlin 1.7.21 にアップグレードしようとしたら謎エラーになるケースの対応方法

kotlin 1.7.10 から 1.7.21 にアップグレードしようとしたら以下の謎エラーが出る怪奇現象が発生していた。

e: java.lang.NoSuchMethodError: 'void kotlin.script.experimental.api.KotlinType.<init>(kotlin.reflect.KClass, boolean, int, kotlin.jvm.internal.DefaultConstructorMarker)'
        at org.jetbrains.kotlin.scripting.definitions.ScriptCompilationConfigurationFromDefinition$1.invoke(ScriptCompilationConfigurationFromDefinition.kt:32)
        at org.jetbrains.kotlin.scripting.definitions.ScriptCompilationConfigurationFromDefinition$1.invoke(ScriptCompilationConfigurationFromDefinition.kt:28)
        at kotlin.script.experimental.api.ScriptCompilationConfiguration.<init>(scriptCompilation.kt:23)
        at kotlin.script.experimental.api.ScriptCompilationConfiguration.<init>(scriptCompilation.kt:25)
        at org.jetbrains.kotlin.scripting.definitions.ScriptCompilationConfigurationFromDefinition.<init>(ScriptCompilationConfigurationFromDefinition.kt:27)
        at org.jetbrains.kotlin.scripting.definitions.ScriptDefinition$Companion.getDefault(ScriptDefinition.kt:221)
        at org.jetbrains.kotlin.scripting.compiler.plugin.ScriptingCompilerConfigurationExtension.updateConfiguration(ScriptingCompilerConfigurationExtension.kt:67)
        at org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment$Companion.configureProjectEnvironment(KotlinCoreEnvironment.kt:578)
        at org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment.<init>(KotlinCoreEnvironment.kt:199)
        at org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment.<init>(KotlinCoreEnvironment.kt:108)
        at org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment$Companion.createForProduction(KotlinCoreEnvironment.kt:445)
        at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.createCoreEnvironment(K2JVMCompiler.kt:192)
        at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:143)
        at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:53)
        at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:99)
        at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:47)
        at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
        at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:475)
        at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:125)
        at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileIncrementally(IncrementalCompilerRunner.kt:373)
        at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileIncrementally$default(IncrementalCompilerRunner.kt:318)
        at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.rebuild(IncrementalCompilerRunner.kt:114)
        at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:207)
        at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:79)
        at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:625)
        at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:101)
        at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1746)
        at jdk.internal.reflect.GeneratedMethodAccessor26.invoke(Unknown Source)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:568)
        at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:360)
        at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200)
        at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197)
        at java.base/java.security.AccessController.doPrivileged(AccessController.java:712)
        at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
        at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:587)
        at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:828)
        at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:705)
        at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
        at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:704)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
        at java.base/java.lang.Thread.run(Thread.java:833)

色々試した結果、spring boot bom の中で kotlin のバージョンを固定しているのが問題なようだった。以下のように dependency management plugin でバージョン指定しているため、spring boot が 2.7.5 だったので、org.jetbrains.kotlin:* のバージョンが 1.6.21 になってしまって、結果として API が齟齬ったようだ。

    dependencyManagement {
        imports {
            mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)
        }
	}

というわけで、以下のようにして解決。

    dependencyManagement {
        imports {
            mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)
            mavenBom("org.jetbrains.kotlin:kotlin-bom:1.7.21")
        }
	}

20221129 追記

    ext["kotlin.version"] = Versions.KOTLIN

とかするのがスマートかも