mizdra's blog

ぽよぐらみんぐ

OK Google, 今日のゆゆ式

この記事はゆゆ式 Advent Calendar 2019 21日目の記事です. 20日目はJonah Wilsonさんのゆゆ式BDを持って星を見に行った話でした.

これはなに

Google Homeに話しかけるとオススメのゆゆ式の1コマを印刷できるIoT, 「今日のゆゆ式」です.

開発者からのコメント

普段の暮らしにゆゆ式を, そんな思いで作ってみました. 11年分のゆゆ式のコンテンツから1回につき1コマだけを, 一期一会のコマたちに思いを馳せて下さい.

どうやって作ってるの

お店で使われているようなレシートプリンタを使って, コマ画像を印刷しています. 予めリクエストを受けるとランダムでコマを選び, レシートプリンタで印刷するWebhookサーバを用意しておき, そのWebhookをGoogle Home経由で叩くようにしています. Google Home<=>Webhookの連携はIFTTTで, Webhookサーバはadnanh/webhook + ngrok で雑に作ってます. 印刷に使うコマの画像は自炊したデータをyonkoma2dataでコマごとに分割して用意しました. 本当はもっと細かく説明したいのですが, 結構複雑なことをやっていて全部説明しようとすると1冊本が出来てしまうので, 作ってみたい人は自分で調べるなりWebhookサーバのソースコードを見て頑張ってみて下さい.

github.com

 

 

スペシャルサンクス

  • えすじさん
    • 単行本の取り込み, コマの分割で大変お世話になりました
  • 三上小又先生
    • いつもお世話になっております

22日目は@Lavendelstraussさんです.

iTerm2 で `cat /dev/urandom` すると印刷ダイアログが出ることがある

皆さんは /dev/urandom と呼ばれるUnixデバイスをご存知でしょうか. /dev/urandom は一言でいうと擬似乱数を出力する疑似デバイスで, catすると以下のようにランダムなバイト列を逐次的に出力してくれます. 出力するバイトの値域に特に制限は無いため, ターミナルに印字不可能な文字が表示されたりします.

良い具合にバイト列が揃うと漢字が流れてきたり, 異国の文字が流れてきたりと眺めているだけでも結構面白いです. 皆さんも是非お試し下さい. 私は絵文字が流れてきたのを見て大喜びしてました.

ところでこの cat /dev/urandom ですが, iTerm2でひたすら動かしていると稀に印刷ダイアログが開くことがあります.

印刷するともれなく異国の文字たちがお出迎えしてくれます

実は cat /dev/urandom しなくても echo コマンドで簡単に再現できます. 試しにお手元のiTerm2で以下のコマンドを実行してみて下さい.

echo '\e[i'

印刷ダイアログは出ましたか? 出た方は記念に1枚プリントしておきましょう (しなくても良いです). 印刷はともかくとして, \e[i という文字列, これは一体何なのでしょうか.

ANSI Escape Sequence

ターミナルにはANSI Escape Sequenceと呼ばれる一部の文字列をターミナルを制御する特別な文字列として扱う機能が存在し, これを用いることでプログラムからターミナルを制御できるようになっています. 代表的なANSI Escape Sequenceには文字色の変更, スクリーンの消去, カーソルの移動などがあります. vimやemacsでカーソルを移動できたり, シンタックスハイライトが効いたりするのはこうした文字列のおかげなんですね.

もちろん \e[i もそうした文字列の1つで, 「表示されている画面を印刷する」よう指示する文字列として機能します. また \e[i には, カーソルのある行を印刷するよう指示する \e[1i, 入力された文字列をプリンタへとechoするモードに切替える \e[5i, モードを元に戻す \e[4i などの亜種があります.

## Hello World が印刷される
echo '\e[5iHello World!\e[4i'

iTerm2における \e[i

元々ANSI Escape Sequenceは1978年に登場したビデオ端末「VT100」を操作するために開発された仕組みで, 現代の端末エミュレータの多くがこれをエミュレートするよう設計されています*1. iTerm2もその内の1つで, リポジトリを「VT100」で検索するとそれっぽいコードがヒットします.

実際に \e[i を処理しているコードを追ってみると, VT100CSIParser.m:673\e[ni (n は任意の数) をトークンへとパースし, VT100Terminal.m:1791でトークンと n を見て印刷ダイアログなどを呼び出しています. よくよく見るとiTerm2では \e[i \e[5i \e[4i の3つの文字列しかエミュレートしていないことが分かったりします.

端末エミュレータによって対応はまちまちのようで, MacOS標準のTerminal.appでは亜種どころか \e[i すらサポートされていませんでした. というか \e[i をサポートしているiTerm2以外の端末エミュレータはそもそも存在するのでしょうか(Macだとこの2つくらいしか動作確認できなかった). 皆さんからの情報提供をお待ちしています.

活用法

Meta+P が壊れて動かなかない時や, どうしても Meta+P を使いたくない時に活用できそうです (本当に?). ダイアログが出て邪魔という点に注目すると, CUIゲームに登場する敵キャラの妨害攻撃とかにも使えそうですね (本当に?). 真面目なケースだとターミナルブラウザにおける window.print() のエミュレートあたりでしょうか.

おまけ

ダイアログ出せると知ったら当然やりますよね.

ちゃんと対策されてました. 20へえ.

*1:https://ja.wikipedia.org/wiki/%E7%AB%AF%E6%9C%AB%E3%82%A8%E3%83%9F%E3%83%A5%E3%83%AC%E3%83%BC%E3%82%BF より. 平成生まれの人間なので詳しくは知りません.

Review AppsでFirebase Authenticationを使いたい

最近趣味で作っているWebアプリをHerokuにデプロイしてReview Appsを導入したところ, Firebase Authenticationと相性が悪くてちょっとハマったという話.

当該のアプリではFirebase Authenticationを使っていて, ログインボタンを押すとTwitterのOAuth認証画面へとリダイレクトし, 承認するとアプリへ戻りログイン状態になるというよくあるSNS認証機能がある. Firebase Authenticationには承認済みドメインという概念が存在していて, これにあるドメインを追加するとそのドメインからの認証を許可することができる. 逆に言えば, 追加されてないドメインからは認証を拒否される. 認証可能なドメインをホワイトリストで制限することで, ユーザを不正なドメインから認証させ, アクセストークンを盗むフィッシングから守ったりしてくれている. 当該のアプリは example.herokuapp.com のようなドメインで配信されているので, それと同じものをFirebase Authenticationの承認済みドメインに登録している.

が, このアプリに対してReview Appsを導入すると問題が発生する. Review AppsはP-Rごとに環境を立ち上げそれらに自動で example-pr-1.herokuapp.com のようなドメインを割り振ってくれるのだが, 当然これらのドメインは承認済みドメインとして設定されてないため, Firebase Authenticationでログインしようとしても弾かれてしまう. Firebase Authenticationのコンソールを開き, 手動で承認済みドメインに1つずつ登録していくのも手ではあるが, 面倒なのであんまりやりたくない. 困った...

とりあえずパッと思いついた解決策を書き出してみる.

  1. サブドメイン herokuapp.com ごと許可してしまう
    • hogehoge.herokuapp.com のようなドメインからのログインを許可してしまい, 誤ってログインしたユーザがアクセストークンを盗まれる可能性がある
  2. 正規表現を使い, 承認済みドメイン example-pr-*.herokuapp.com を登録する
  3. pr-*.example.app のようなドメインを持つプロキシサーバを立て, 認済みドメインに example.app を登録する
    • サーバの用意が面倒なのが難点
  4. 自動で承認してもらうのを諦め, ログインが必要な機能の検証をしたい時だけ手動でドメインを承認済みドメインに追加する
    • コードを一切変更しなくて良い
    • ただし承認済みドメインの追加, 及び不要になったドメインの削除を全て手動でやらなければならない
  5. 開発環境で利用できるログインボタンを追加する
    • 開発環境用のFirebase Authインスタンスを新規に作成し, そのインスタンスを使ってログインできるボタンを追加で設置する方法
    • 開発用と本番用の認証データを分けるというのがポイント
    • 開発用Firebase Authは開発者しか使わないので, 承認済みドメインのスコープがゆるくても問題ない
      • 開発者自身が怪しいドメインでのOAuth認証を拒否すれば良い
      • 開発環境用のFirebase Authインスタンスの承認済みドメインには herokuapp.com を, 本番用には example.herokuapp.com を設定しておく
      • こうすることでReview Appsで自動生成されるドメインからもログインでき, かつユーザを危険な状態に晒さずに済む
    • 開発環境向けログインボタンは example-pr-*.herokuapp.com では見えるようにし, example.herokuapp.com では隠すようにしておけばユーザから触られる心配もない
    • 一方で開発用と本番用で別々のuidが発行されるので, Review Appsで本番用のユーザデータを使って動作確認できないという問題がある
      • これについては 4 の方法と組み合わせてカバーできそう

3は全てを解決してくれるが多少のガッツが必要でだるい. あとあまりメンテナンスするアプリケーションを増やしたくない... 4はめちゃめちゃ素朴だけど, 個人で開発しているのだったらこれでも十分っぽい. 今回扱っているプロジェクトも個人で開発しているものなので4だけでも十分だけど, DependabotでドカドカP-Rが立ち上がったり, e2eテスト導入してみたいという事情があったためシュッと動作確認できる5も採用することにした.

実装してみた感じでは, 手作業不要でサクッと動作確認できるのは最高. が, 今どちらのfirebase authインスタンスに接続しているかを判定できるようログイン時にlocalStorageに接続先の情報を保存する必要があったりして, 持ち込む複雑さや実装の手間の割にメリットが微妙. 5を採用するかどうかは (少なくとも個人開発しているプロジェクトでは) かなりケースバイケースになりそうだなーという感想です.

ゆかりスロットをWeb Share APIに対応させた

iOSのSafariにWeb Share APIが来るという話を聞いたので, 趣味で作っている「ゆかりスロット」で試してみることにした. 2月2日時点でまだiOS 12.2はリリースされていないので, Stableな環境で動かしたいならChrome 61+がインストールされたAndroid端末が必要.

developers.google.com

yukari-slot.mizdra.net

www.mizdra.net

実装の詳細

navigator.sharetitle, url, text の3つのoptionalなパラメータを渡して呼び出すと, 共有可能なアプリ一覧が載ったOSネイティブのあのシートが画面下から生えてくる. それぞれのパラメータの対応状況は共有先のアプリに依存していて, 処理されたり無視されたりする. 自分が試した限りではSlack for Androidは3つとも見てくれたけど, Twitter for Androidは texturl しか見てくれなかった.

function shareResult(leftEye: number, rightEye: number) {
  const text =
    leftEye === 1 && rightEye === 1
      ? 'ゆかりちゃん完成!!!'
      : 'ゆかりスロット失敗 😥'
  const url = `https://yukari-slot.mizdra.net/share/${leftEye}${rightEye}`

  navigator
    .share({
      title: 'ゆかりスロット',
      text,
      url,
    })
    .then(() => console.log('shared!'))
    .catch((e) => console.error(e))
}

navigator.share に渡す引数が不正の場合は TypeError , ユーザアクション契機による呼び出しでなければ NotAllowedError , シェア先のアプリが無い場合やユーザがシェアをキャンセルした場合は AbortError が投げられる. ゆかりスロットでは navigator.share がない環境やシェアに失敗した場合はは Twitter Web Intentでシェアするようfallbackしている.

function createShareData(leftEye: number, rightEye: number) {
  const text =
    leftEye === 1 && rightEye === 1
      ? 'ゆかりちゃん完成!!!'
      : 'ゆかりスロット失敗 😥'
  const url = `https://yukari-slot.mizdra.net/share/${leftEye}${rightEye}`
  return { text, url }
}

function createTweetLink(text: string, url: string) {
  const encodedText = encodeURIComponent(text)
  const encodedHashtags = encodeURIComponent('ゆかりスロット')
  const encodedUrl = encodeURIComponent(url)
  // Twitter Web Intentの場合は hashtags パラメータでハッシュタグを設定する
  return `https://twitter.com/intent/tweet?text=${encodedText}&hashtags=${encodedHashtags}&url=${encodedUrl}`
}

async function share (
  leftEye: number | undefined,
  rightEye: number | undefined,
) {
  const { text, url } = createShareData(leftEye, rightEye)

  try {
    await navigator
      // ハッシュタグを付加して共有
      .share({ text: `${text} #ゆかりスロット`, url })
  } catch (e) {
    // AbortError はユーザがシェアをキャンセルした場合の
    // エラーなので無視する
    if (e.name === 'AbortError') return

    // navigator.share がない環境やシェアに失敗した場合は
    // Twitter Web Intentにfallbackする
    window.open(createTweetLink(text, url))
  }
}

今の所雑に text パラメータにハッシュタグを含めているのでSlackにシェアするとチャンネルへのリンクになって残念なことになっている. 真面目に実装するならTwitterにシェアするボタンとWeb Share APIを呼び出すボタンは別々にしてユーザが選択できるようにすると良さそう.

f:id:mizdra:20190202020352p:plain:w300
Slackにシェアした様子. ハッシュタグが上手く機能していないことが分かる.

ローカルでデバッグする

Web Share APIは (現状Stableなバージョンでは) Chrome for Androidでしか動かないため, Android端末を用いてデバッグすることになる. またAPIはhttpsなホスト or localhost にのみ提供されており, Web Share APIに対応しているブラウザであってもhttpなホスト上で navigator.share を参照すると undefined が返ってくる. つまり, APIをローカルでデバッグするには開発マシン上の開発サーバに対して, Android端末から localhost ホストでアクセスする必要がある.

これはChrome Dev ToolsのPort Fowarding機能を使うと簡単に実現できる. Remote devices タブの Settings から以下のように開発マシンのportとモバイルデバイスのportのマッピングを指定できる.

f:id:mizdra:20190202020023p:plain
開発マシンの localhost:8080 とAndroid端末の localhost:8080 をマッピングしている様子

developers.google.com

『プログラミングRust』輪読会における取り組みについて

はじめに

この記事はwhywaita Advent Calendar 23日目の記事です.

adventar.org

今日はwhywaitaさんと僕が所属している技術系学生サークル「MMA」ので行われている活動である『プログラミングRust』輪読会について, 開催に至った経緯とその様子を紹介します.

『プログラミングRust』輪読会 とは

弊サークルでは部員の技術力向上, 部員同士の交流などを目的に, 部員有志の間で技術勉強会が定期的に開催されています. とある分野を勉強したい部員が集まってその分野について勉強するという形式で, ただ黙々と作業するものから講義スタイルのものまで様々です. 以下のようなテーマの勉強会がここ数年で開催されてきました.

  • Haskell講習 (2016/4 〜 2016/6)
    • Haskellの入門書であるすごいH本を読み進めていく会
  • Scala Collection Library Code Reading (2017/4 〜 2017/8)
    • Scalaのコレクションライブラリのコードを読む会
  • CTF勉強会 (2017/4 〜 現在)
    • 2017年は講義形式, 2018年はCTFの常設問題に取り組みCTFの勉強をしている
    • メンバー同士でオンラインコンテストへ出場
  • 競技プログラミング勉強会 (2018/11 〜 現在)
    • 競技プログラミングの常設問題を一緒に解いたり, アルゴリズムの勉強をする
    • メンバー同士でオンラインコンテストへ出場
  • 『プログラミングRust』輪読会 (2018/10 〜 現在)

『プログラミングRust』輪読会はこの勉強会の一環として開催されているものです. 元々, 僕がRustという言語を勉強したくて『プログラミングRust』を書籍を購入したのですが中々読む時間が取れず, いわゆる積読本となっていました. また書籍自体もとても分厚く (約600P), またRustという急な学習曲線を持つ言語がテーマであることも読書を躊躇する理由となっていました.

プログラミングRust

プログラミングRust

  • 作者: Jim Blandy,Jason Orendorff,中田秀基
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2018/08/10
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る

流石にそのまま放置していても良くないので, 何かしら手立てを考えて読み進める方法を模索することにしました. そこで取った選択肢が「輪読会」です. 個人で分厚い本を読むと途中で飽きたりしがちですが, 輪読会駆動で定期的に読む仕組みを取り入れることで, 読書を強制することができます. それ以外にも以下のような様々な効果が期待できると言われています.

  • 一人で読むにはパワーが必要な本を全員で読みきる
    • 分担して読むことで負担を減らせる
  • 理解が難しい本を全員で理解する
    • 普段触れていない分野が題材の場合でも、有識者の補足をもらえる
    • 別の分野の視点から良いところ、悪いところなどの意見や議論ができる
    • より実際に運用しているサービスに当てはめて考えやくすなる
  • 触れたことのない分野を皆でキャッチアップ
    • 全員が一度に知識をキャッチアップできるので属人化になりにくい

輪読会がうまく回りはじめた話 - LCL Engineers' Blog

幸いにも部員の中にRustに興味がある人が多数居たので, すぐに開催へと漕ぎ着けることができました.

輪読会を始める

開催に関するコンセンサスが取れたので, 輪読会のルールについて決めていきます. 具体的には開催日はどうするか (週1か週2か隔週か), 1回あたりの分量はどうするか (節ごとか章ごとか), 音読するか, 予習は必須か, 予習した範囲を要約した資料を課題として作るかどうか, などといったことを決定します. これらは「実際に本の中で紹介されているコードを試しながら読み進めて欲しいので予習必須」, 「分量が多いので要約資料必須」など, 本の特性に合わせて決めていくと良いかと思います. 今回は以下のような基準で輪読会のルールを決めていきました.

  • スケジュールが調整しやすいので週1ごと開催
  • 言語機能ごとにまとめて学びたいので章ごと読む
  • 1章あたりの分量が多いので音読は無し
  • 本の中で紹介されているコードを試しながら読み進めて欲しい&当日読む時間が勿体無いので, 予習は必須
  • 分量が多いので要約資料必須
  • 属人化を防ぐため, また主催の負荷を軽減するため要約資料は持ち回り制の担当者が作成

ルールが決まったら, 輪読会の概要と共にアナウンスを流します. この際, 輪読会の難易度や雰囲気を明確に伝えるため, またミスマッチを防ぐため「対象者」という項目を書いておくのがポイントです. 以下は実際にサークルのメーリングリストに流したアナウンスです.

「『プログラミングRust』輪読会」開催のお知らせです. Rustの入門書が最近発売されたのですがとっても分厚いので,
輪読会駆動で頑張って読み進めようという会です.

- 輪読する本: https://www.oreilly.co.jp/books/9784873118550
- Rustについて: https://employment.en-japan.com/engineerhub/entry/2017/07/10/110000

Rustは"非常に急な"学習曲線を持つ難しい言語ですが, 沢山人が集まれば誰かしらは分かっている人が居るので質問し合いましょう.
議論して様々な視点から理解を深め合えるかも.

参加希望者は以下の調整さんから都合の合う日を入力して下さい :pray:

- https://chouseisan.com/s?h=XXXXXXXXXXXX

## 概要
- 輪読会駆動で『プログラミングRust』を最後まで読む
    - 毎週1章ずつ読んで半年〜1年で全章読破という気持ち
- 開催日
    - 週1で開催予定
    - 調整さんの結果を見て決めます
- 対象者
    - 参加は自由ですが, プログラミング初心者にはRustは難しすぎるのでオススメしません
    - 以下のような経験があると初学が少し楽になります
        - C/C++などのシステムプログラミング言語を触ったことがある
        - 式指向言語を触ったことがある
        - 関数型プログラミングを取り入れた言語を触ったことがある
        - パターンマッチングがある言語を触ったことがある
        - ジェネリクス(ジェネリック/総称型)がある言語を触ったことがある
- 場所: 部室 (予定)
- 持ち物: 『プログラミングRust』の物理本または電子書籍 (各自で用意)
- 連絡用Slackチャンネル: #programming-rust


## 進め方
- 当日は質問や議論に集中しましょう
- そのために…
    - 参加者は予習をお願いします :pray:
        - 参加者は当日扱う範囲を事前に読んできて下さい
        - 完全に理解できなくてもOK. 1人で無理せず当日質問して解決しましょう
    - "担当者"は予習範囲の要約をお願いします
        - 担当者は予習範囲を要約したものを当日持ってきて下さい
        - 当日は主にその内容を元に議論をします
        - 担当者は毎週持ち回りします
- 当日の流れ
    1. 担当者が要約した内容について話す
    2. 要約を元に皆で議論する


## 補足
- 予習について
    - 予習範囲で分からないことがあったらSlackで聞いたり, メモして当日共有すると良いです
        - 1人で悩む時間は勿体無いので相談しよう! きっと誰か教えてくれる!
- 要約について
    - 形式は自由
        - `.txt` でも `.md` でもOK
        - スライドでもただの文章でもOK
        - 箇条書きでもOK
    - 本に書いてあることを要約に全部含める必要はないです
        - 自分が特に重要だと思った所を要約すると良いかも
        - 元の文章の主張や全体像を簡単に把握できればOK
            - 詳細は書籍を当たれば良い


## 第1回の案内
- 読む範囲: 1〜2章
- 担当者: @mizdra
- 開催日
    - 決まり次第 #programming-rust に流します
    - 早ければ来週から, 難しければ再来週から

輪読会の進行の仕方

当日になったら部室に集まって, 初めに要約資料を担当者が読み上げます. 要約と言っても分量が多い章だとA4サイズで30Pくらいあるので, 要約資料もそれなりの分量になります. そのため, 大抵は質問&小休憩タイムは最後にまとめて取るのではなく, 節ごとに挟みながら30分〜80分程度掛けて進めています. 要約資料の読み上げが終わったら, 途中で拾いきれなかった疑問やちょっとした余談, 全体を通した感想などについて議論していきます. 要約資料の読み上げ中にSlackチャンネルに予めネタを放り投げておくと, 最後の議論タイムで話すネタをスタックできたりして便利です.

f:id:mizdra:20181223234620p:plain
6章の要約資料の読み上げ中のやり取り

実際にどのような質問と議論が行われたのかは, 第一回の輪読会の資料にログが残してあります *1. 以下で輪読会の資料の一部が公開されていますので, 参考までにどうぞ.

感想や反省など

まだ輪読会は7章までしか進んでいませんが, 各自が予習し, 資料を作成し, それを元に議論するというフローが上手く回っており, 無事軌道に乗ることができているようです. 自分は以前からTRPLでRustの勉強をしていて多少のRustの知識はあったのですが, 実際に書籍を読んだり他の参加者に指摘されたりして自分の誤解に気づく, という場面も何度かありました. 「お互いに理解を深め合う」ということが実現できており, 開催して良かったなと思っています.

一方で質問や議論のログが残っておらず, 輪読会に来ていない/来れなかった人に知見を共有できていない, 振り返りの時間が確保できていないなど反省点もあります. 今後はこうした改善点をきちんと洗い出し, 次回以降の勉強会に役立てていきたいですね.

以上, whywaita Advent Calendar 23日目の記事でした. 24日目は @yu_suke1994 さんの担当です!

*1:他の回は質問の議事録まで取っていなかったため, 残念ながら残っていません.

日向縁さんの誕生日をお祝いしてゲームを作った話

はじめに

この記事はゆゆ式 Advent Calendar 2018 9日目の記事です.

adventar.org

今回は日向縁さんの誕生日をお祝いしてゲームを作ったので, その紹介をします.

日向縁さんと目

皆さんは「日向縁」というキャラクターを聞いて初めに何を連想するでしょうか. ゆるふわ, お姫様, よく笑う, などなど. 人によって様々なイメージが浮かぶと思います. ちなみに僕は「1の目」を最初に連想しました. そう, 日向縁さんといえば作中でよく数字の「1」のような目をすることで知られています. 原作者の三上小又氏のツイートなどで「1の目」をネタにされていることで印象深い方も多いでしょう.

また, 日向縁さんの誕生日が11月11日であることに因んでこんなイラストも描かれています.

ゆかりスロット

かくいう僕も日向縁さんの誕生日をお祝いしてこんなゲームを作ってみました.

yukari-slot.mizdra.net

日向縁さんの目を揃えるスロットゲームです. 任意のタイミングで「とめる!」ボタンを押すと任意の数字が目として揃っていきます. 「11」が揃えば完成です.

またスロットの結果をシェアしたいというご期待に答えて, シェアボタンを用意してあります. 皆さんのお気に入りの縁さんをシェアできる最高便利機能です.

こんな感じで画像付きでツイートできる.

難易度調整に少し拘っていて, 目で追える速度で回っているけど中々狙って止められない程度になっています. 何度も挑戦して是非完成させてみて下さい.

開発時のちょっとした小ネタ

こちらはスロットを停止させた時に止めた目の取得に失敗して, 片目が undefined になった「undefinedゆかり」です.

f:id:mizdra:20181209232800p:plain:w400

そしてこちらはレイヤの重ね合わせの順番をミスった結果誕生した「ゆかりモンスター」です. 怪談とかに出てきそう.

f:id:mizdra:20181209233124p:plain:w400

おわりに

以上が ゆゆ式 Advent Calendar 2018 9日目「日向縁さんの誕生日をお祝いしてゲームを作った話」となります. いかがでしたでしょうか. ただ「11」を揃えて完成させるのを狙うだけでなく, 色んな目を揃えて日向縁さんの表情を楽しむのも良いかもしれません. 皆さんも是非ゆかりスロットで遊んで貰えればと思います.

10日目は@ekme_brbrさんの担当です!

ポケットモンスター・ポケモン・Pokémon・は任天堂・クリーチャーズ・ゲームフリークの登録商標です.

当ブログは @mizdra 個人により運営されており, 株式会社ポケモン及びその関連会社とは一切関係ありません.