閑古鳥

オールドプログラマの日記。プログラミングとか病気(透析)の話とか。

今年買ってよかったもの・十三機兵防衛圏

特に思いつかないなぁと思ったけど、ひとつあった。十三機兵防衛圏、PS4のゲームだけど、これは今年一番のゲームです。SF好きな人は買いましょう。

十三機兵防衛圏 - PS4

十三機兵防衛圏 - PS4

  • 作者:
  • 出版社/メーカー: アトラス
  • 発売日: 2019/11/28
  • メディア: Video Game

SF要素てんこ盛りのアドベンチャー+タワーディフェンスのゲーム。ゲームをはじめるといきなり崩壊編と呼ばれるタワーディフェンスと同時に物語が始まる。チュートリアルステージをいくつかクリアすると追想編と呼ばれるアドベンチャーパートが遊べるようになり、そこで崩壊編の操作キャラクターの回想が見られる。この時点ではプレイヤーにはほとんど情報が開示されておらず、また操作キャラもほどんと情報を持っていない状態ではじまり(お約束?の記憶喪失のキャラもいる)、主人公達と同じ目線で物語の謎を追っていくことになるんだけど、これがめちゃくちゃ面白い。

崩壊編の名の通りいきなり世界の危機から始まるけど、なぜ"それ"が起こったのか。13人(!)もいる主人公の過去、関係が各々の視点で徐々に開示されていくのだけど、この情報の出し方がうまくて、すぐに引き込まれて続きが気になって仕方無い。途中で「ああ、そういうアレか」と納得したと思ったらひっくり返されるみたいなことも多く、またAの視点で見えた伏線とBの現状が噛み合って無くないか?みたいな謎もプレイすると出てきて、辞め時がなくなります。

タワーディフェンスパートも爽快感あって、またノーマルだとほどよい難易度という感じでこちらも楽しかったです。プレイ時間は45時間くらい、後半は一気にプレイしてクリアしましたが、気持ちよく終われてたいへんよいゲームでした。ザッピングはないけど「街 〜運命の交差点〜」なんかを連想しました。群像劇で視点だけでなく時系列も飛びまくるので作るのは本当に大変だったろうな…と思います。追うのも少し大変かもしれませんが、それに見合う快感は得られるし、その複雑なところを究明編など見て追いかけるのもまた楽しいので、ぜひ。

null合体演算子 ?? の優先順位

何も考えずに使ったらハマったのでメモ。

int? foo = null;
(5 + foo ?? 0); // => "5"を期待したけど"0"になる

これは:

(5 + (foo ?? 0)); // => 5

と書きましょう。StyleCopで乗算や除算には問答無用で括弧を付けろと言われる環境なので油断していました(何も言われないので)。

vscode-journalで初回の「Open Today(今日のファイルを開く)」コマンドの実行が遅い / のでコマンドを作ってしまった

VSCodeを起動してjournal.todayコマンドを実行すると、数秒待たされる。原因はmdファイルを初めて開いた時にMarkdown関係の拡張が有効化されるが、それが終わるまでコマンドが完了しないため。
普通にmdファイルを開くと、ファイルを開いて編集可能になってから拡張の読み込みが始まるので待たされていると感じない。この快適さがほしい。

vscode-journalで使うファイルの拡張子をmd以外にするとか、Markdownの拡張を減らすという回避策もあるけど、それは残念感が強い。Emacsのごとく最初だけなんだから気にしない、というのがベターっぽいけども、暇なので少し調べてみた。

ソースを見るとどうやら:

const doc = await vscode.workspace.openTextDocument(path); // ファイルを開いて
vscode.window.showTextDocument(doc);                       // アクティブにする

みたいなコードを実行すると拡張の読み込みが終わるまで処理を返さないらしい。VSCodeにわかなので何もわからないのだけど、openTextDocumentを待たずにonDidOpenTextDocumentイベントを使うといいのだろうか?

いっちょvscode-journalのソース書き換えてPRでも送ってみようかと思ったけど、ソースを読むとopenTextDocumentの戻り値で得られるドキュメントにテンプレート書き込んだりしていて、案外変更量多くなりそう……と気付いたらvscode-powertoolsで自前のコマンドを作ってしまいました。VSCodeではファイルを開く方法がいくつかあるみたいで、上記の他に

vscode.commands.executeCommand("vscode.open", vscode.Uri.file(ファイル名));

でも開けて、こちらは拡張の読み込みを非同期で行ってくれるバージョンのようなので、こっちを使ったコマンドを作ってみました。

journal.js

Power Tools - Visual Studio Marketplace

まずPower Toolsをインストールして、 ~/.vscode-powertools/直下にjournal.jsファイルを作ります。

// ~/.vscode-powertools/journal.js
function getJournalFile(args, offset) {
    const vscode = args.require('vscode');
    const path = args.require('path');
    const moment = args.require('moment');
    const config = vscode.workspace.getConfiguration("journal");

    moment.locale(config.get('locale')); // journal.locale

    const base = config.get('base'); // journal.base
    const ext = config.get('ext'); // journal.ext
    const day = moment(Date.now()).add(offset, 'days');
    const parent = path.join(base, day.format('YYYY'), day.format('MM'));
    return [day, parent, path.join(parent, `${day.format('YYYYMMDD')}.${ext}`)];
}

async function openJournal(args, offset) {
    const vscode = args.require('vscode');
    const fs = args.require('fs');
    const fsExtra = args.require('fs-extra');

    const [day, parent, fileName] = getJournalFile(args, offset);

    if (!fs.existsSync(fileName)) {
        fsExtra.mkdirpSync(parent);

        const config = vscode.workspace.getConfiguration("journal");
        let template = config.get('tpl-entry');  // journal.tpl-entry
        template = template.replace('${localDate}', day.format('LL'));
        template = template.replace('${weekday}', day.format('dddd'));
        fs.writeFileSync(fileName, template, err => console.error(err));
    }

    await vscode.commands.executeCommand("vscode.open", vscode.Uri.file(fileName));
}

async function pickJournalOffset(args) {
    const disposables = [];
    try {
        return await new Promise((resolve, _) => {
            const vscode = args.require('vscode');
            const input = vscode.window.createQuickPick();
            input.items = [
                {label: 'Today', parsedInput: 0, alwaysShow: true},
                {label: 'Yesterday', parsedInput: -1, alwaysShow: true},
                {label: 'Tomorrow', parsedInput: +1, alwaysShow: true}
            ];

            input.onDidAccept(() => {
                const offset = input.value.length === 0 ? input.selectedItems[0].parsedInput : parseInt(input.value);
                resolve(offset);
            }, disposables);

            input.onDidHide(() => {
                input.dispose();
            }, disposables);

            input.show();
        });
    } finally {
        disposables.forEach(d => d.dispose());
    }
}

exports.getJournalFile = (args, offset) => {
    const arr = getJournalFile(args, offset);
    return arr[arr.length - 1];
}

exports.open = async (args, offset) => {
    openJournal(args, offset);
};

exports.execute = async args => {
    const offset = await pickJournalOffset(args);
    openJournal(args, offset);
};

settings.jsonにコマンドを追加。

// settings.json
"ego.power-tools.user": {
        "commands": {
            "myjournal.open": { // 名前は何でもいい
                "name": "Open Journal file",
                "script": "journal.js",
            },
        }
    }

keybindings.jsonにショートカットを追加。

{
    "key": "ctrl+shift+j",
    "command": "myjournal.open"
},

これでCtrl+Shift+Jを押すとメニューが表示され、選んだ日のファイルが開けます(なければ作る)。

journal-prev.js

ついでに「今開いているファイルの1日前のファイルを探して開く」コマンドも作りました。Ctrl+Shift+Alt+Pにショートカットを割り当てて、連打すると昨日のファイル、一昨日のファイル、一昨昨日のファイル…と辿って見られるようにしました。あれ何日前に書いたメモだっけ、みたいな捜し物をするときに使います。(こういうの、howmとかなら検索で解決するんだろうなぁ)

// ~/.vscode-powertools/journal-prev.js
exports.execute = async args => {
    const vscode = args.require('vscode');
    const fs = args.require('fs');
    const path = args.require('path');
    const journal = require('./journal.js');

    const currentFile = vscode.window.activeTextEditor.document.uri.path;
    let offsetDays = 0; // today
    if (currentFile) { 
        // YYYYMMDD -> YYYY-MM-DD (決め打ち)
        const fileName = path.basename(currentFile, path.extname(currentFile));
        const date = new Date(`${fileName.substr(0, 4)}-${fileName.substr(4, 2)}-${fileName.substr(6, 2)}`);
        offsetDays = Math.round((Date.now() - date.getTime()) / (1000*60*60*24));
    }

    // 直近10日前まで遡って、最初に見つかったファイルを開く
    // (getJournalFileの効率が悪いし、1ヶ月空いてもいいようにしたいし、もうちょっとうまくやりたい)
    for (let i = 1; i <= 10; ++i) {
        const offset = -(i + offsetDays);
        const fileName = journal.getJournalFile(args, offset);
        if (fs.existsSync(fileName)) {
            vscode.commands.executeCommand("vscode.open", vscode.Uri.file(fileName));
            return;
        }
    }
};
// settings.json
"ego.power-tools.user": {
    "commands": {
        "myjournal.previous": {
            "name": "Open Previous Journal file",
            "script": "journal-prev.js",
        }
    }
}
// keybindings.json
{
    // Ctrl+Shift+Alt+←(right) にしようとしたらなぜか動かない🤔
    "key": "ctrl+shift+alt+P",
    "command": "myjournal.previous",
}

BOOK☆WALKERの読み放題サービス

角川文庫・ラノベ 読み放題|1万冊以上のライトノベル・角川文庫が月額760円(税抜)から読み放題!電子書籍ならBOOK☆WALKER

いわゆるサブスク。月760円+税で一定の本が読み放題になる。月2冊くらい読めば元が取れるので、結構お得なのではなかろうか。

しかし音楽にしろ動画にしろ、ユーザにとってはいいけど定額制でちゃんと儲かるんだろうか…と余計な事を気にしてしまう。このサービスの場合、シリーズものの最初の方だけ読めるようになっているものがあって、いわゆる体験版とか試し読みサービスとして考えるとアリなのかな? という気もする。

1月まで無料で試せると言うことでとりあえず加入してみたけど、今のところブラウザでしか読めないみたいで、AndroidのBook WalkerアプリでもWebView経由になってしまうのが惜しい。PCで読むならいいんだけど、スマホ版はもう少し使いやすくしてほしいな。アプリのリーダーで読ませる場合本のダウンロードが必要で、そうなると「読み放題でなくなった本をどう扱うか」みたいな問題がありそうで簡単ではないかもしれないけど。

だんだん厚着になっていく

小学生の頃は冬でも半袖みたいな子供だったけど、徐々に寒さに弱くなってきて着る物が増えていっています。常にセーターとか上着用意したり、最近は指の部分だけ露出した手袋という中二っぽいアイテムも買ってしまった。指先が出ていても、手の平や手首を暖められるだけでも結構違うみたいで、案外良いです。

寒がりになったの、なぜか30過ぎてから平熱が37度代になったのも影響しているのだろうか。そのおかげか風邪は全然引かなくなりましたが、もともと暑いのがダメで夏が苦手なのに冬もダメになってきて困ります。

calmemo(xyzzy)からvscode-journalに移行してみた

xyzzyというエディタのcalmemoという拡張を2009年くらいから、かれこれ10年使い続けている。

日記帳みたいなもので、コマンドを実行すると今日の日付のテキストファイルを作ってくれてそこに何か書ける、というもの。それに検索とかカレンダー機能などが付いている。xyzzy自体は学生の頃からなので20年近く使い続けているけど、そんな昔からあるアプリだけあって非常に軽量で使いやすく、日報とかまとめるのに使っていました。令和になった今でも、Windows 10でも当たり前のように動くし特に不満もなかったのですが、最近絵文字入れたいよなぁ……と思うようになり、VSCodeで書こうかな、と考え表題に。

vscode-journalVSCodeの拡張で、これもコマンドを実行すると今日の日付のファイルを作ってくれるもの。ほぼ書くだけの用途にしか使っていなかったので、機能的には十分そうです。

アプリも何年も使っていると愛着が湧くのか乗り換えるのに妙に抵抗を感じてしまうものですが、フォルダ構成やファイル名のルールを合わせておけばこれまでのcalmemoのログと同じところにログを残せるし、エイヤで乗り換えてしまおう。

vscode-journalにカレンダーはないけど、vscode-jounal-viewという別の拡張を入れるとサイドバーにツリー形式で既存のログを一覧できるみたい。でも、ツリーは一覧性が微妙かな? 昨日の日報、一昨日の日報…と辿ってみたいという需要が希にあり、そういうのが面倒そう(昨日とか、特定の日に飛ぶコマンドはあるけど、連続しては飛べない)。まぁ、必要なら自分で作ればいいか。TypeScriptならCommon Lispより書けるし。

MSTestのCollectionAssertでdoubleの配列を比較する際に精度を指定したい

Assert.AreEqualは精度を指定できるメソッドがあるけれど、CollectionAssertにはないっぽい。まぁ、それはそうか。

// Assert.AreEqualのオーバーロード:
public static void AreEqual (double expected, double actual, double delta);

IComparerを引数にとるメソッドがあるので、これが使えそう。しかし、どうせならIComparer<T>にしてほしかった…。

public class DoubleComparer : IComparer
{
  public int Compare(object x, object y)
  {
    return (x, y) switch
    {
      (double dx, double dy) => Math.Abs(dx - dy) < double.Epsilon ? 0 : dx.CompareTo(dy),
      _ => -1
    };
  }
}

public void TestMethod()
{
  double[] excepted = ...;
  double[] actual = ...;
  CollectionAssert.AreEqual(excepted, actual, new DoubleComparer());
}