まだプログラマーですが何か?

プログラマーネタ中心。たまに作成したウェブサービス関連の話も https://twitter.com/dotnsf

2020/01

各種メディアのニュースでも話題になっていますが、香川県が「香川県の子どもたちがネット・ゲームの長時間の利用により、心身や家族的・社会的な問題を引き起こすネット・ゲーム依存に陥らないために必要な対策を推進し、健全な育成を図るため、本条例を制定しようとするもの」という条例の素案を示しました。具体的には18歳未満の場合、1日にオンラインゲームは60分(土日・祝日・長期休暇中は90分)以内と規定する方針のようです(ただし子供にも保護者にも罰則なし)。


この内容で気になったのは、香川県内のルールだけでなく「ゲーム提供事業者に求める役割」が規定されようとしている点です。具体的には以下の第11条部分が相当します:
香川県の「ネット・ゲーム依存症対策条例案」【全文】

2020012501


※なお、この第11条に相当する事業者および香川県在住者とを対象に2月6日までパブリックコメントを受け付けています:
香川県ネット・ゲーム依存症対策条例(仮称)素案についてパブリック・コメント(意見公募)を実施します


「インターネットを利用して情報を閲覧に供する事業またはコンピュータゲームのソフトウェアの開発、製造、抵抗等の事業を行う者」をどのように解釈するかにもよりますが、僕自身も個人でオープンソースのゲームを作ったり、ネットで誰でも遊べる状態で公開したりしています。例えば来週の IT 勉強会の題材にもしようとしているスライドパズルゲームとかもその1つです。以下の URL にアクセスするだけで遊べてしまいます(遊び方は後述):
https://dotnsf.github.io/slidepuzzle/


↑こういう趣味開発レベルのゲームも「県民のネット・ゲーム依存症の対策に協力するものとする」の対象になっちゃうんですかね? という意見をパブリックコメントで伝える権利を有するのかな?(有する場合は協力しないといけないってこと??)


・・などなど、まあ色々言いたいこともありますが、とりあえず自分なりに考えた上で対策を講じてみました。対策済みのゲームはこちらから遊べます:
https://kawaga-game.mybluemix.net/


上記 URL にウェブブラウザでアクセスすると、以下のような位置情報へのアクセス許可を促すメッセージが表示されるので「許可」を選択してください(以下は FireFox を使った場合の画面です):
2020012502


利用者(=あなた)が香川県以外からアクセスしていた場合、以下のような画面になり、そのままゲームを遊ぶことができるようになります。ゲーム自体はカメラアイコン部分をクリックして、PC内に保存されている画像を選びます:
2020012503


すると選んだ画像が懐かしいスライドパズルとなって遊べる、というものです。一応移動回数やゲームを解くまでの時間がカウントされていて、競技性のある作りにしています:
2020012504


しかしアクセス元が香川県であった場合のみ少し挙動が変わり、画面ロード直後に以下のような警告画面が表示されます。これで上述の第11条で求められる対策努力が講じられているのではないかと自負しております(笑):
2020012505


このゲームのソースコードもこちらで公開しています:
https://github.com/dotnsf/kagawa


具体的な実現方法としては HTML ページの中で GeoLocation API の getCurrentPosition メソッドを実行して現在地の緯度・経度を取得しています。そして取得結果に対して Yahoo! デベロッパーネットワークYahoo! ジオコーダ API を使って緯度・経度を住所に変換し、その住所が「香川県」で始まっているかどうかを識別する、というロジックで実現しています。

少しだけ補足すると、アクセス元の識別はできているので、単なる警告に留めずに実際に利用時間を測定して、1時間を超えることがあったら強制終了、みたいなことができないわけではありません。まあこのゲームに限っては1時間超えても飽きずに遊んでもらえる可能性はかなり低いと思ってますけど・・


・・・どなたかの参考になれば幸いでございます。 (^^;

"Hash File Storage" という、(IBM Cloud を使って)無料でも運用できるウェブストレージサービスのソースコードを公開します:
https://github.com/dotnsf/hfs


もともとはマンホールマップという自作の位置情報付き画像投稿サービスの機能の一部として開発したものだったのですが、画像投稿機能部分を切り出して、かつハッシュ計算を加えた上で API を整備しました。基本ストレージとして IBM Cloudant を使いますが、IBM Cloud のライトアカウント(無料)の範囲内でランタイム含めて運用可能なので、よかったら IBM Cloud と合わせてお使いください。


機能そのものは「ファイルストレージ」です。用意されたサンプルページや API を使ってファイルをアップロードしたり、アップロードしたファイルをダウンロードしたり、というよくあるものです。各種機能を REST API や Swagger ドキュメントでも提供しており、容易に外部アプリケーションから呼び出して利用することも可能です。

最大の特徴は格納時のファイル ID をファイルバイナリのハッシュ値で管理している点です。したがって既に登録されているファイルと(ファイル名などは異なっていても)バイナリレベルで全く同じファイルを登録しようとすると、同じファイル ID が既に存在しているため「登録できない」というエラーが返ります。またファイルを登録する以外にも「このファイルと同じものが既に登録されているか?」だけを調べる API が用意されていて、一度登録した後になんらかの変更が加わっているか/いないかを ID(ハッシュ値)で調べることができる、という特徴があります。 このサービス自体には含まれていませんが、ブロックチェーンと連携することでバイナリファイルの真偽性保証や、対改ざん性の強化を実現するものです。


実際に動作を確認するにはソースコードを git clone するかダウンロード&展開し、IBM Cloudant のクレデンシャル情報を指定した上で Node.js で起動します。詳しくは README.md を参照ください。



Swagger を使うことで、インタラクティブに実行可能な REST API のドキュメントを作ることができます。比較的有名な所では Swagger がライブデモとして用意している「ペットストア・サービスの REST API」があります:
https://petstore.swagger.io/

2020012201



↑架空の「ペットストア・ウェブサービス」が存在している前提で、ペットやその画像を登録/更新したり、検索したり、、加えてペットストア自体の登録/管理をしたり、利用者となるユーザーの管理といったペットストア運営に必要と思われる最低限の機能の REST API が実行可能な状態で公開されています。


アプリケーションを REST API ベースで作る際に、このような Swagger による API ドキュメントを用意しておくと、REST API の仕様が確認できるだけでなく、実際にパラメータを指定した上で実行したり、実行した時のデータのやりとりやその結果までを確認することができて便利です。REST API を作成した時にセットで作っておくと、開発後の運用時や機能追加、引き継ぎといった段階においても非常に役立つインタラクティブドキュメントだと思っています。


この Swagger ドキュメントは REST API 開発後に swagger.yaml (または swagger.json)というファイルを用意することで自動作成が可能になります。特定のルールに基づいて REST API の内容(ホスト、パス、メソッド、パラメータ、返り値、・・)を記述していくと、ドキュメントの UI は自動的に作成され、指定した通りに実行が可能になります(そして実際に実行された結果を確認することもできるようになります)。

なお上述のペットストア・サービス REST API の定義内容はこちらの Swagger Editor で確認できます:
https://editor.swagger.io/

2020012205


swagger.yaml の記述方法そのものについては本ブログエントリの範囲ではないので、興味ある方は別途調べていただきたいのですが、そこまで難しい内容ではないと感じています。例えば GET /users?limit=xx&offset=yy といった具合に2つの URL パラメータ(limit, offset)を指定可能(必須ではない)な GET の REST API が存在していて、これを Swagger ドキュメントに記述する場合は swagger.yaml ファイルに以下のように指定します:
  :
/users
  get
    parameters:
      - name: limit
        type: number
        in: query
        description: 取得するデータ数
      - name: offset
        type: number
        in: query
        description: 取得するデータのオフセット
  :

実際に Swagger ドキュメントとして生成すると、この部分は以下のような UI に変換されて指定可能になる、といった具合です:
2020012202



で、ここからが本エントリの本題です。この Swagger ドキュメント(の yaml ファイル)を記述する際の、特に「ファイルアップロード」の API を記述する場合のパラメータ指定方法が特殊でちとわかりにくいものでした。試行錯誤した上で実際に動く例を見つけたので、自分の備忘録も兼ねて以下に紹介します。

まず API そのものは単純なファイルアップロード(POST /file)と仮定します。受け取るパラメータはアップロードするファイル本体だけ、他のパラメータは(Swagger ドキュメント的には特別な部分ではないので)いったん無視します。Node.js + Express 環境であれば、以下のような内容で作られているようなものです:
//. multer モジュールを利用してアップロードファイルを受け取る
var express = require( 'express' ),
    multer = require( 'multer' ),
    router = express.Router(),

    :

//. multer モジュールの設定
router.use( multer( { dest: './tmp/' } ).single( 'file' ) );

    :

//. POST /file として REST API を用意する
router.post( '/file', function( req, res ){
  //. アップロードされたファイルの情報を取り出す
  var filepath = req.file.path;
  var filetype = req.file.mimetype;

    :
    :


UI 側からこの API を呼ぶ時は、普通に以下の様な enctype="multipart/form-data" を指定した form の HTML を用意すれば実行できるものです:
<form method="POST" action="/file" enctype="multipart/form-data">
<input type="file" name="file"/>
</form>

↓こんな感じの見た目になって実行されるものです:
2020012203

このような挙動を行う API : POST /file を Swagger ドキュメントとして用意する場合の(パラメータ部分の)記述方法をどのようにするか、というのが今回のテーマでした。

ちょっと特殊な指定方法となりますが、結論としては以下のようになります:
  :
/file:
  post:
    consumes:
      - multipart/form-data
    parameters:
      - in: formData
        name: file
        type: file
        description: アップロードするファイル
        required: true
  :

特殊な部分を解説します。まず HTML の form 属性で enctype="multipart/form-data" 指定を行っていた部分ですが、上述のように consumes: で "multipart/form-data" を指定することで実現します。

またパラメータ部分ですが、POST のデータとして送信するので "in: formData" を指定します。またパラメータの種類として "type: file" も付与します。 加えて、このパラメータは API 実行時には必須指定パラメータとなるので "required: true" も付与します。swagger.yaml ではこのように指定することでファイルアップロード系の API を記述できます。

参考程度に、このように指定した swatter.yaml を実際に Swagger ドキュメントに変換して表示すると、以下のような画面になりました:
2020012204


期待通りの結果になりました。

便利な地図表示ライブラリ Leaflet(と OpenStreetMap)を使って表示する地図は原則的には上が北になります。これを変更して(つまり地図を回転させて、上が北側にならないようにして)表示することに挑戦してみました。

最初は「やり方をググれば見つかるだろう」と軽く考えていたのですが、これはどうやらかなり難しいらしく、Stackoverflow で同様の質問を見つけましたが、ここでの回答には「Leaflet ネイティブで用意された方法は存在しない」「簡単な方法はない」とされています。かなりハードル高そうです。。

ネイティブには用意されていない、、簡単ではないものを自力で解決する、、 言うは簡単ですが実際には細かな調整含めて結構面倒なものでした。ただ一応サンプルとして動く形にできましたので、公開&紹介します。

まず、ソースコードはこちらに用意しました。実装は index.html ファイルを参照してください(バックエンドなしにこのファイル1つだけで実装しています):
https://github.com/dotnsf/om


実際に動く様子を確認・体験できるように github ページも用意しました。スマホのブラウザでこちらにアクセスしてください:
https://dotnsf.github.io/om/


なお、このページでは DeviceOrientation イベントをハンドリングしているため、環境によっては事前準備が必要です。こちらのページでも紹介しているように Android や iOS 12.1 以下の場合は事前設定不要です(ページを表示すればそのまま動きます)。 iOS 12.2 以上 13.0 未満の場合は URL 先にアクセスする前に予め Safari の設定で「モーションと画面の向きのアクセス」を ON にしておいてください(この設定後に URL にアクセスすれば動きます):
2020011001



また iOS 13.0 以上の場合は事前の準備は不要ですが、ページロード直後の初期状態で表示される「センサーの有効化」というボタンをクリックしてから「許可」してください:
2020011002


いずれの環境においても上記の準備を済ませた上でアクセスすると、東京を中心とした関東の地図が表示されます。また画面の中心には赤字でこのスマホデバイスの横方向への傾き角度が表示されます:
2020011701


スマホを横方向に傾けると、その傾きに合わせて地図が画面内で回転して表示されます。下図ではスマホを向かって右側に(時計回り方向に)傾けています。スマホの傾きに関係なく、常に真上(下図では左上方向)が北になります:
2020011700


実際に動かしている様子を動画にしました:



以下、技術的な解説を加えていきます。

まず CSS で body など地図を表示する要素のサイズを width も height も 100% に指定して、全画面で地図が表示されるようにしています。

そして回転処理そのものは地図を表示する <div> 要素に CSS の transform: rotate() 属性を適用して実現しています。今回の例では <div id="demoMap"> 内に Leaflet 地図を表示していますが、デバイスの傾き(角度: deg)を検知して、
 $('#demoMap').css( 'transform', 'rotate(-'+deg+'deg)' );
という処理を実行することで <div> ごと回転をかけて表示しています(加えて deg の数値を <div id="me"> 内で表示していますが、こちらは特別な処理をしていないため、詳細は省略します)。

ただこれだけだとうまくいきません。Leaflet の仕様なのか、CSS transform の仕様なのか「もともと表示されている要素が回転して表示される」らしく、縦 100% 横 100% の画面を回転させると、角度によっては表示されない部分が出来てしまうのでした:
2020011801



↑黒がスマホ本体(黒枠の中だけが実際に表示される)、左側は本体画面に地図が表示されている。スマホを回転させて表示すると、元の地図の矩形部分は表示されるが、表示しきれない部分(黄色の部分)が生じる。実際には黄色部分はグレーで表示されてしまいます。


この状況を回避するための工夫を加えています。具体的には <div id="demoMap"> のサイズを width, height ともに 200% に指定して実際のスマホ画面の4倍の面積のエリアをロードするようにしています(更に left と top を -50% にして、そのロードしたエリアの中心部分だけを表示するよう調整しています。こうすることで表示された画面の中心を軸に画面が自然に回転するようになります)。細かいことですが、この仕様に合わせて <div id="me"> の位置もこの変更に合わせて調整したり、(表示位置がズレないよう)画面にスクロールバーが出ないよう CSS で抑制したりしています:
2020011802


↑地図部分を実画面サイズよりも大きくすることで、回転させても表示不可エリアが生じないように調整しています。


こうした工夫によってなんとか実現できました。ただ地図上に書かれた文字が斜めになってしまうのはさすがにどうしようもないです。その辺りは地図 API 側でサポートしてくれるのを待つしかないかなあ。。

なお index.html 内の JavaScript 変数 lat(緯度), lng(経度)が地図の中心点座標、zoom がズームレベルです。これらの値を変えると表示するエリアや拡大縮小レベルを変えることができますので、自分の環境で挑戦してみたい方は是非挑戦してみてください。


Jタウンネットが行った「じゃんけんのグー・チョキ・パー、どれが一番好き?」という興味深い質問への読者投票がまとめられていました:
https://j-town.net/saitama/research/results/300363.html?p=all


この結果によると、最も人気が高かったのは「チョキ」で、得票率はなんと半数近い 47.8%! 続いて「グー」の 30.5% 、最下位が「パー」で 21.7% とのことでした。 多くの場合、じゃんけんは「相手がグー・チョキ・パーを同じ確率(約 33.3 %)で出してくる前提で語られることが多いので、この結果は少なからず衝撃的でした。。
town20200116182739


個人的にはかなり意外な結果でした。チョキが一番複雑な手の形になるので一番少ないのではないかと思っていたので。。

仮にこの結果の通りに相手が選んでくるとした場合、「自分はじゃんけんで何を出すべきか?」という命題に対する答えが、従来の「何を出しても一緒」ではなくなってしまいます。

期待値計算をしてみましょう。以下簡略化のため「グー」を G、「チョキ」を C、「パー」を P と表記することにします。

相手の出してくる手と、その確率をおさらいすると以下のようになります:
 G: 0.305
 C: 0.478
 P: 0.217

また期待値計算をする上での自分の利得値を以下のように仮定します:
 勝ち = 1
 負け = -1
 あいこ = 0

※グーで勝っても、チョキで勝っても、パーで勝っても同じくらいに嬉しく、
 グーで負けても、チョキで負けても、パーで負けても同じくらいに悔しく、
 あいこは勝ちと負けの中間の嬉しさ(悔しさ)である、というゼロサムゲームな仮定です。


この場合の自分の選ぶ選択肢と、それぞれの期待値は以下のようになります:
 G : 0.305 * 0 + 0.478 * 1 + 0.217 * -1 = 0.261
 C : 0.305 * -1 + 0.478 * 0 + 0.217 * 1 = -0.088
 P : 0.305 * 1 + 0.478 * -1 + 0.217 * 0 = -0.173

つまり期待値では G(グー)が一番高く(しかも唯一のプラス)、次が C(チョキ)、最下位が P(パー)ということになりました。この統計を前提とするとグーを出すのが戦略的には有利、ということになるのでしょうか。。


このページのトップヘ