画像を連鎖で消す

この記事は[クソアプリ Advent Calendar 2022](https://qiita.com/advent-calendar/2022/kuso-app
)の22日目の記事です。

みなさん、『ぷよぷよプログラミング』はご存知でしょうか。
セガが提供している、JavaScriptでぷよぷよを実装するプログラミング教材です。
無料で学ぶことができます。

https://puyo.sega.jp/program_2020/

先日地元のプログラミングコミュニティ[鹿児島.mk](https://kagoshima-mk.connpass.com/)でこれを題材にした体験会を実施しました。

https://zenn.dev/kurehajime/articles/c174305742aeb5


## というわけで画像を連鎖で消すやつを作ってみました

ぷよぷよを実装していくうちに、色んなものを連鎖で消したい欲求がムクムクと湧き上がってきたので、任意の画像を連鎖で消すやつを作ってみました。

こういう画像があるとします。

![before](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33890/56adff75-943d-6878-0774-b4ed0f82ad52.png)

これを連鎖で消すやつを作りました。

![bayoencode2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33890/2537c68a-305a-c293-ed25-1ad612d59022.gif)

## なにをしているのか

このプログラムは以下のような動作をしています

* 画像を100x100pxに縮小する。
* 画像の色を16色に減色する。 
* 同じ色が4ドット以上隣接しているドットを消す。
    (ただし消える場所が複数ある場合は一番大きい塊だけ消す)
* 空いた領域を落下させる。

最初はぷよぷよと同じく4つ以上の塊を一度に全部消す実装にしていたのですが、一瞬でほぼすべてが消失したので「一番大きい塊だけ消す」という制約を加えました。これがないとあまりにも虚無です。

## 実装の中身

あくまで学習目的で公開されているぷよぷよのソースを参考にしているので、ソースおよびサービスは非公開です。

とはいえ中の実装は色々と工夫しているので、やってることを大まかに解説します。


### 画像を100x100pxに縮小する

これは特に工夫せず、100x100pxに縮小してCSSで拡大しています。
なぜ100x100なのかというと、処理が重すぎるからです。
最初500x500で実装したところ、ぷよを1回消すために30秒ほどかかりました。
本当は1920×1080で「フルHDぷよぷよ作りました！」と言いたかったのですが、残念ながら最適化が間に合わず100x100になっています。

### 画像の色を16色に減色する

単純にwebcolorのパレットなどと使って減色すると色が汚くなるので、https://lokeshdhakar.com/projects/color-thief/
というライブラリを使って代表的な色を抽出しています。

たとえば冒頭の画像からパレットを作るとこんな感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33890/2b1e3d8f-7193-9497-0f14-2fe3429c28aa.png)

[街の写真](https://publicdomainq.net/center-gai-shibuya-tokyo-0067771/)のような色数が多い場合でも代表的な色を抽出してくれます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33890/628f28bb-f118-555c-5d40-4f813623e791.png)

### 同じ色が4ドット以上隣接しているドットを消す & 空いた領域を落下させる

これがメインロジックです。

#### アルゴリズム

当初は[ぷよぷよプログラミング](https://puyo.sega.jp/program_2020/)を参考に実装していました。
しかしもとのコードは再帰で実装されており、これのせいでスタックオーバーフローが発生したので[再帰を使わないアルゴリズム](https://qiita.com/takahasinaoki/items/e576860edc59ed61be73)で実装し直しました。

実はこの再帰を使わないアルゴリズム、自分は過去にも実装したことがありました。

[![187020236-9ecc4891-ab42-4acf-962c-fcee9cd63a16.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33890/2d0dffb5-7679-54bb-f5c8-d81556a29846.gif)](https://kurehajime.github.io/minesweeper/)

マインスイーパーで爆弾のないマスを自動でピッと開く処理がまさにこれです。ペイントソフトのバケツツールで塗りつぶすのもこれです。ぷよぷよとは塗りつぶしなのです。

React+SVGで作ってます。ぜひ遊んでください。

https://kurehajime.github.io/minesweeper/

#### Rust & WebAssembly

今回のプログラム、実装する前からやばいくらい処理が重くなるのは予想されていたので、Rustで実装しWebAssemblyで組み込みました。

自分はこのプログラムを作る前に別のプログラムをWebAssemblyで実装したのですが、そちらはあまりパフォーマンスが上がらなかったので、WebAssemblyは銀の弾丸ではなかったかも知れません。

https://zenn.dev/kurehajime/scraps/536e7414bb1ce6

## おわりに

ぷよぷよの実装って普段Webプログラミングする時にはあんまり使わない手法が結構使われてるので、なかなか新鮮味があって面白いです。みなさんもぜひ[ぷよぷよプログラミング](https://puyo.sega.jp/program_2020/)に挑戦してみてはどうでしょうか。


それでは最後にAIに作ってもらったお気に入りの3枚の画像を連鎖で消したいと思います。

クリックすると崩壊します。

[![before](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33890/56adff75-943d-6878-0774-b4ed0f82ad52.png)](https://user-images.githubusercontent.com/4569916/208908815-0d25f78c-d25d-43ff-9b21-d9f0229b2569.gif)

[![teacher](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33890/104c7cd0-70b2-6755-17fb-172d9e5bce1e.png)](https://user-images.githubusercontent.com/4569916/208908851-aebeff23-17cb-4305-a7cc-24486b710a79.gif)

[![color](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33890/2068aef9-947e-9ae2-7a69-8fb41c05373c.png)](https://user-images.githubusercontent.com/4569916/208908861-ae5ef879-aa52-4291-9382-e6175add1cdf.gif)