無駄と文化

実用的ブログ

WordPress でピクロスを作問・投稿できるカスタムブロックを作った

ピクロスというパズルをご存知でしょうか。お絵描きロジック, ののぐらむ などとも呼ばれる、縦横に並んだ数字をヒントにドット絵を完成させるパズルゲームです。
WordPress でピクロスを作問・投稿するためのカスタムブロック wp-block-nonogram を作成したので紹介します。

 

目次

 

wp-block-nonogram はこんなやつ

プラグインを有効化すると WordPress の編集画面で "Nonogram Puzzle" というブロックが使えるようになります。
そして、

作問の様子

ブロックエディタでドット絵が打てるようになります。

作問が完了して投稿を公開すると、

解答している様子

記事のページ上でピクロスを遊ぶことができます。

 

使い方

GitHub でプラグインの ZIP ファイルを公開しています。
ダウンロードして展開し wp-content/plugins ディレクトリに配置した後に管理画面からプラグインを有効化すると "Nonogram Puzzle" ブロックが使えるようになります。

github.com

WordPress 公式のブロックディレクトリに登録するために現在申請中です。
申請が通ったら、管理画面上からもっと手軽にブロックを使い始められるようになるはずです。

 

本文の編集領域にカーソルを置いた状態で /nonogram と入力するか、ブロック追加から "nonogram" で検索すると "Nonogram Puzzle" ブロックが挿入されて作問できるようになるはずです。

プラスマークのボタンからブロックを選択して挿入してもいい

行数・列数を自由に増減させて作問できます。ブロックは1記事に複数配置することも可能です。

行数・列数は自由に変更できる

作問が終わって記事を保存して公開すれば、サイトを訪れたユーザーが問題を解くことができます。

 

なぜこんなものを作ったの

非実用的なネタブロックを作るのに憧れがあったからです。
WordPress におけるカスタムブロックは、ほぼただの React コンポーネントです。そのため JavaScript で実装されたゲームならそのままブロックエディタ上で動作させられます。

ja.wordpress.org

私が好きな非実用ブロックはこの ピアノブロック です。ブログ用の記事編集からかけ離れすぎなコンセプト、やたら高機能なところが最高です。

私もなにかネタブロックを作りたいと思い、ちゃちゃっと実装でそうな題材としてピクロスを選びました。
が、ちゃんと作問と投稿ができる実用的なブロックに仕上がってしまったことを反省しています。

 

実装上のおもしろポイント

はじめて <canvas> を本格的に使ってブラウザ上で動くゲームを作ったので楽しかったです。(感想)

最初は『縦横に並んだセルを塗ったり消したりするなら <input type="checkbox"> を並べたらええな!』と思っていたのですが。縦30セル × 横30セル の問題を作るとチェックボックスを900個配置することになるので無理過ぎました。
いくら仮想 DOM をもつ React といえども取り扱いうタグが増えるほど差分管理が重くなるので、パフォーマンス観点から <canvas> を使うことにしました。

<canvas> にあれこれ描画するには konva と (それを React 向けにラップした) react-konva というライブラリを使っています。
特に react-konva は、まるで HTMLElement を扱う感覚で <canvas> に描画した図形を扱えて便利でした。

 

今後の展望

もっともっと非実用的なブロックを作りたい!という思いがあるので、次回作としてマインスイーパーブロックを作ろうかなと構造しています。
編集画面でマインスイーパーが遊べて、記事の方ではゲームの結果が表示される (ので自慢できる) というようなブロックになる予定です。

 

 

私からは以上です。

指定したキーだけを省略可能にするユーティリティタイプを書く

TypeScript は便利だ。型検査で値が保証されるのはとても頼もしい。
とはいえ場合によっては型検査を通すために不必要にタイプ量が増えてしまうことがある。

例えば下記のような型が 外部ライブラリによって生成される としよう。

type User = {
    __typename: 'user';
    id: string;
    name: string;
    gender: 'male' | 'female';
    department: {
        __typename: 'department';
        id: string;
        name: string;
    };
};

いま User 型の値を生成して使いたい。ただし、とても重要な前提条件として キー __typename には関心がない とする。

const user: User = {
    __typename: 'user',  // このキーは使うつもりがないし関心もない
    id: 'u001',
    name: 'SUZUKI Ichiro',
    gender: 'male',
    department: {
        __typename: 'department',  // このキーは使うつもりがないし関心もない
        id: 'd001',
        name: 'sales',
    }
};

このような値を定義すれば型検査をパスする。 が、使うつもりのない __typename をわざわざ書くのは面倒だと思った、そう仮定しよう。

 

特定のキーを省略可能にしたい

type User においてキー __typename は必須だった。もしも何らかの方法で以下のような型 BetterUser を生成できたら。

type BetterUser = {
    __typename?: 'user';
    id: string;
    name: string;
    gender: 'male' | 'female';
    department: {
        __typename?: 'department';
        id: string;
        name: string;
    };
};

__typename?: となっているのに注目してもらいたい。キー __typename は省略可能なので以下のような値でも受け入れられる。

const user: BetterUser = {
    // __typename を書かなくて済む
    id: 'u001',
    name: 'SUZUKI Ichiro',
    gender: 'male',
    department: {
        // __typename を書かなくて済む
        id: 'd001',
        name: 'sales',
    }
};

いかにも良さそうだ。__typename を省略可能にするにはどうすればいいだろうか。

 

ユーティリティタイプでどうにかする

既存の型をもとに別の型を生成する 型レベル関数 のようなものを TypeScript においては「ユーティリティタイプ」と呼んでいる。1

例えば、全てのキーを省略可能するユーティリティタイプ Partial<T> は次のように書ける。

type Partial<T> = { [K in keyof T]?: T[K] };

動作イメージはこうだ、

type User = {
    __typename: 'user';
    id: string;
    name: string;
    gender: 'male' | 'female';
};

type PartialUser = Partial<User>;
// => type PartialUser = {
//     __typename?: 'user' | undefined;
//     id?: string | undefined;
//     name?: string | undefined;
//     gender?: 'male' | 'female' | undefined;
// };

実はこのようなユーティリティタイプ Partial<T> は TypeScript に最初から用意されている。そのためわざわざ定義しなくても使える。とても便利だ。

だが今回は 指定したキーだけ 省略可能にしたいのだった。全てのキーを省略可能する Partial<T> は少しやりすぎだ。

 

Partial<T> をそのまま使うことは出来なそうだが「指定したキーだけ省略可能にするユーティリティタイプを定義して使う」という方針は決まった。
先に名前を決めてしまおう SelectivePartial というユーティリティタイプが欲しい。

type SelectivePartial<T, K> = {};  // TODO: 実装する

type BetterUser = SelectivePartial<User, '__typename'>;

このように書けると良さそうだ。

 

Partial を応用した単純な例

指定したキーだけ省略可能にするのはわりと簡単で、ChatGPT に頼むとサラッと書いてくれる。

type SelectivePartial<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

このように。

Omit<T, K>Pick<T, K> も TypeScript に標準のユーティリティタイプだ。Omit<T, K>K で指定したキーを T から除外してくれる。Pick<T, K> はその逆で、K で指定したキーのみ T から残してくれる。

type User = {
    __typename: 'user';
    id: string;
    name: string;
    gender: 'male' | 'female';
};

type Omitted = Omit<User, '__typename'>;  // User から __typename を除外
// => type Omitted = {
//     id: string;
//     name: string;
//     gender: 'male' | 'female';
// };

type Picked = Pick<User, '__typename'>;  // User から __typename だけを残す
// => type Picked = {
//     __typename: 'user';
// };

これと先ほどの Partial<T> を組み合わせて、さらに & でつないで交差型 (Intersection Types) を作ると「指定したキーだけ省略可能にする」が実現できる。

type Parted = Partial<Pick<User, 'name'>>;  // name だけを残し、省略可能にする
// => type Parted = {
//     __typename?: 'user' | undefined;
// };

type BetterUser = Omitted & Parted;
// => type BetterUser = {
//     __typename?: 'user' | undefined;
//     id: string;
//     name: string;
//     gender: 'male' | 'female';
// };

一連の処理をまとめて型変数を使って書くと最初に示した SelectivePartial<T, K> の定義になる。

type SelectivePartial<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

type BetterUser = SelectivePartial<User, '__typename'>;
// => type BetterUser = {
//     __typename?: 'user' | undefined;
//     id: string;
//     name: string;
//     gender: 'male' | 'female';
// };

最低限の要件を満たしてはいるが不満が残る。この方法ではネストしたオブジェクト型に再帰的に作用させることはできない。

 

SelectivePartial を再帰的に作用させたい

最初の例をもう一度見てみよう。最初に示した User 型はこんな感じだった。

type User = {
    __typename: 'user';
    id: string;
    name: string;
    gender: 'male' | 'female';
    department: {
        __typename: 'department';
        id: string;
        name: string;
    };
};

SelectivePartial<T, K>User に作用させると次のようになる。

type SelectivePartial<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

type BetterUser = SelectivePartial<User, '__typename'>;
// => type BetterUser = {
//     __typename?: 'user' | undefined;
//     id: string;
//     name: string;
//     gender: 'male' | 'female';
//     department: {
//          __typename: 'department';
//          id: string;
//          name: string;
//     };
// };

department の中の __typename はあいかわらず省略できない。

 

ユーティリティタイプを再起的に作用させるには、関数の再帰呼び出しのアイデアを借用すればいい。と、その前に Omit<T, K>Partial<Pick<T, K>> を自前の実装で書き直す必要がある。
Omit<T, K>Pick<T, K> とても似たコードで実装できる。

type Omit<T, K> = {
    [L in keyof T as L extends K ? never : L]: T[L];
};

type Pick<T, K> = {
    [L in keyof T as L extends K ? L : never]: T[L];
};

どちらの実装も L in keyof T の部分で T の各キーに対して作用を起こしている。
Omit の方では L extends K ? never : L で「キー LK に含まれていればキー L を消し去る」という作用を表現している。Pick はその逆で、L extends K ? L : never で「キー LK に含まれていなければキー L を消し去る」という作用を表現している。

Pick<T, K> の自前実装をもとに Partial<Pick<T, K>> も自前で書き下したい。じつはこれはとても簡単で、キーの後ろに ? を打って省略可能であることを表明するだけだ。

type PartialPick<T, K> = {
    [L in keyof T as L extends K ? L : never]?: T[L];
};

ここまでのコードをまとめて前の節で書いた再帰的でない SelectivePartial<T, K> を書き直してみよう。

// type SelectivePartial<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

type SelectivePartial<T, K> = {
    [L in keyof T as L extends K ? never : L]: T[L];
} & {
    [L in keyof T as L extends K ? L : never]?: T[L];
};

ここまでくればユーティリティタイプを "再帰呼び出し" するのは簡単だ。T[L] の部分をちょっといじってあげればよい。

type SelectivePartial<T, K> = {
    [L in keyof T as L extends K ? never : L]: SelectivePartial<T[L], K>;
} & {
    [L in keyof T as L extends K ? L : never]?: SelectivePartial<T[L], K>;
};

改良版の SelectivePartial<T, K> を使ってみよう。

type User = {
    __typename: 'user';
    id: string;
    name: string;
    gender: 'male' | 'female';
    department: {
        __typename: 'department';
        id: string;
        name: string;
    };
};

type BetterUser = SelectivePartial<User, '__typename'>;
// => type BetterUser = {
//     __typename?: 'user' | undefined;
//     id: string;
//     name: string;
//     gender: 'male' | 'female';
//     department: {
//         __typename?: 'department' | undefined;
//         id: string;
//         name: string;
//     };
// };

かなり良くなった。

 

配列型にも適用したい

もしも最初に手元にあるのが User 型ではなく Users 型だったらどうなるだろうか。

type Users = {
    __typename: 'user';
    id: string;
    name: string;
    gender: 'male' | 'female';
    department: {
        __typename: 'department';
        id: string;
        name: string;
    };
}[];

先ほどとよく似ているが [] がついている。「オブジェクト型の配列」型だ。先ほど定義した SelectivePartial<T, K> を使って Users から BetterUsers を作れるだろうか。

 

TypeScript の型についてのちょっとしたテクニックを知っていれば T[]T を相互変換できる。

type A = T[];
type B = A[number];
//   ^? T

type C = T;
type D = C[];
//   ^? T[]

T[] 型の A に対して A[number] とすると T 型が得られる。慣れないと奇妙な感じがするが、イデオムとして覚えてしまうと便利だ。
C = T に対して C[] と書くと T[] が得られるのは、まぁ、自明だろう。

 

このテクニックを使うと SelectivePartial<T, K>Users に適用できるようになる。

type Users = {
    __typename: 'user';
    id: string;
    name: string;
    gender: 'male' | 'female';
    department: {
        __typename: 'department';
        id: string;
        name: string;
    };
}[];

type User = Users[number];

type BetterUser = SelectivePartial<User, '__typename'>;

type BetterUsers = BetterUser[];
// => type BetterUsers = {
//     __typename?: 'user' | undefined;
//     id: string;
//     name: string;
//     gender: 'male' | 'female';
//     department: {
//         __typename?: 'department' | undefined;
//         id: string;
//         name: string;
//     };
// }[];

もちろんひとまとめにして次のように書いても同じ意味になる。

type BetterUsers = SelectivePartial<Users[number], '__typename'>[];

 

まとめ

Omit<T, K> & Partial<Pick<T, K>> というテクニックは ChatGPT がすぐに教えてくれた。それを再帰的に適用するにはいくつか発想の転換が必要だった。

いまはスッキリ理解できていても TypeScript を書かずにいる期間が空くとすぐに筋力が衰えてしまう。少しでも忘却に抗うためにここに書き記す。

 

 

私からは以上です。

 

おまけ

今回書いたコードがここにある、


  1. TypeScript にはさまざまな 組み込みのユーティリティタイプ がある、知っておくといつか何かの役に立つかもしれない

毎年3日間だけ花粉症に罹っている【定点観測】

ここ数年、春先に鼻炎になる。「花粉症か」「自分にもついに来たか」と思って薬を飲むのだが3日ほどすると治っている。そんなことが続くので冗談めかして「毎年3日間だけ花粉症になるんです」などと言っていた。

 

2024年、今年も花粉症の季節がやってきた。

そろそろ実態を解き明かしたい気持ちがあるので、定点観測1年目という気持ちでブログに書き残そうと思う。

 

◾️2024-02-25 sun

朝からやたらと目が痒かった。数週間前に結膜炎に罹っていたので、それが再発したか?と思いながら薬局で薬用の目薬を買って挿した。13時頃。

 

車で30分ほど走って実家に顔を出した。この時点でくしゃみが連続で出るようになっていた。ムズムズが止まらない。ただこの時点では風邪の初期症状のような気もしたし、実家でひさしぶりににペットの犬と触れ合ったことによるアレルギーのような気もした。

 

夜、入浴したら少しだけ落ち着いた。とはいえくしゃみは出る、鼻水も出る。鼻詰まりで顔がポッと火照る感じがする。何らかの鼻炎だなと思い薬局で鼻炎薬を買って飲んだ。21時頃。

 

深夜、喉が痛みだした。唾を飲み込むときに喉にゴロゴロとした違和感と痛みがあり、よく眠れなかった。

 

◾️2024-02-26 mon

朝から調子が悪い。目の痒み、鼻水、喉の痛みが相変わらずで、これは花粉症というやつでは?とかなり確信を得た。もともとハウスダストのアレルギー持ちでくしゃみが止まらなくなることがあったが、目や喉まで同時にやられるのは初めての経験だった。さらに言うと頭がボーっとして集中できない、深い思考が要求されるようなタスクができないなど、かなり苦痛だし仕事にも支障が出ていた。

再び薬局に行きアレルギ製の鼻炎薬を買って飲んだ。15時頃。

 

夜、服薬のおかげで喉の痛みは和らいだが、頭がふらつく感覚があった。昨晩は喉の痛みで寝不足だったのでそのせいかと思いながら就寝。

 

◾️2024-02-27 tue

朝からだいぶ弱っていた。元気が出ない、気力が湧かない、鼻が詰まる、頭がふらつく。ああこんな状態が数週間続くのかと軽く絶望しながら仕事を開始した。

相変わらず思考を要するタスクは思うように進まない。だんだんと眩暈がしてきて生唾が出るようになってきた。12時頃。

(私にとって生唾が出るのは吐き気を伴う体調不良の前兆だった)

 

昼休みを長めにもらってベッドに横になった。何となく寒気がする気がして羽毛布団に毛布を重ねて潜り込んだ。しっかりと暖かいセットアップのはずなのに横になっていて寒さを感じる。悪寒がするというやつで、花粉症ではなく風邪かもっとひどいウイルス性の何かを疑い始めた。15時頃。

 

横になって少しは症状が和らいだ気がしたので、起き上がって本日中に最低限終わらせておきたいタスクだけ消化することにした。終わったのは18時頃。

 

早めに退勤して夕食を済ませて、私用の Web 会議に参加して、風呂に入った。不思議なことに体調がかなり回復しているのを感じた。目の痒みはおさまり、鼻は通っている、喉の痛みもほぼない。ふらつきや吐き気もほぼ意識しなくてよくなっていた。22時頃。

もしかしてアレルギー性の鼻炎薬が体調に合わずにフラフラになっていたのか?と疑って、その日の晩の薬は飲まずに寝た。

 

◾️2025-02-28

朝起きる。完全回復という感じではないが、昨日に比べるとだいぶ体調が良いのを実感する。何よりも頭が働く。ちゃんとした思考ができる。13時頃。

 

結局、薬を飲まないままくしゃみも喉の痛みも治ってしまった。2024年度の私の花粉症さ治った。

 

 

いったいこれは何なんだ?経過を見て判断しようと思う。