2019年5月7日

初めてElmで何か作ってみた。- エニグマ(風)暗号機

最近子どもが学校の図書館かどこかで暗号に関する本を読んだらしく、

 「お友達のAちゃんと秘密のメッセージを交換したいの。エニグマみたいなので。」

とせがまれました。

いきなりエニグマ暗号機の名前が出てきたのでびっくりしましたが、

 「よし、エニグマ(風)暗号機を作ってあげよう!」

ということになりました。




この際なので、以前から試してみたかった、Elmを使うことにしました。

出来たモノはこちらに公開してあるのでもし良ければ試してみて下さい。

https://mysecretmsg.mikehi.now.sh/




伝えたいメッセージを打ち込んでEnterキーを押すと、「暗号化キー」の入力画面になります。



ランダムな4桁の数字を入力してもう一度Enterキーを押すと、暗号化されたメッセージが表示されます。



これを相手に伝えます。

暗号化されたメッセージを受け取った相手は、同じアプリを開いて暗号文を打ち込み、さっきと同じ4桁の「暗号化キー」を入力すると復号化されたメッセージを見ることが出来ます。


暗号化されたメッセージを同じキーで再度暗号化すると元の平文に戻る(暗号化と復号化のプロセスが同じ)」、というのが「エニグマっぽい挙動」ということになるかと思います。


これをどうやって実現しようかと考えて少し悩みました。

単純に文字コードのXORを取れば出来そうなのですが、それだと結果が文字として表示出来ない値になってしまう事があるので、別の方法を考えました。

その結果、

- 入力可能な文字を2つのグループに分けて、2文字単位で「置換ペア」を予め決めておく。
- このような置換ペアのテーブルを10種類作っておく。
- 「暗号化キー」の各桁(0〜9の数字)を置換ペアのテーブルに対応させて、順に置換する。

という簡単な方法で実装してみました。


このアプリを使って秘密のメッセージを送る際には、メッセージとキーは別々の方法で伝える必要があります。(キーだけは事前に合意しておく、メッセージはメール、キーは口頭で伝える、など)

メッセージ本文と暗号化キーの両方を知っている人は誰でも復号化して元のメッセージを見ることが出来るので、キーを秘密にしておくことはとても重要です。(当たり前ですが。)


今回のソースコードはこちらにあります。

https://github.com/mikehibm/mysecretmsg




さて、完成したアプリを子どもに使わせて見たところ、予想外の感想が返って来ました。


「やっぱり...本物のEnigmaが欲しい!」



追記:

Elmを初めて使った感想を何も書いていなかったので思い出しながら書きます。

- 最初慣れるまでは結構大変。アプリケーションのひな形も Sandbox, Element, Document, Application とあってどれを使ったら良いのか迷った。

- 特にキー入力をどうやって処理するのか、ドキュメントを見てもよく分からず。Githubのサンプルとにらめっこして何とか動かせた。Decoderとか今でも完全には理解してません。笑

- 関数の集まりとしてアプリケーションが宣言的に定義出来てしまうというのは、やはり目からウロコ的な体験で面白かった。

- 上位レベルの関数から順に追っていけばプログラム全体が見通せるので、誰が書いてもそこそこ読みやすくなりそうな気がする。

- parcel + Elmの組み合わせでホットリローディングが出来るので、これはオススメ。








.

2019年4月4日

Visual Studio 2019をインストールしたらASP.NET Webアプリケーションが動かなくなった問題のとりあえずの対応方法


Visual Studio 2019が正式リリースされたとのことで、早速インストールしてみました。


AIによるサジェスト機能とかはあまり期待していないですが、全体的に動作が軽くなるそうで、もし本当ならそこは嬉しいです。


Visual Studio 2017 (15.9.11)がすでに入っている環境に、追加で入れました。


インストール完了後、既存のASP.NET Webアプリケーションをデバッグ実行しようとすると、次のエラーが出て実行出来なくなってしまいました。

Could not load file or assembly 'Microsoft.VisualStudio.Web.PageInspector.Loader, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified.


このエラーメッセージをそのままGoogle検索して、下のページに解決策を見つけました。


https://developercommunity.visualstudio.com/content/problem/398640/could-not-load-file-or-assembly-microsoftvisualstu-7.html



まずは Visual Studio Installer を起動して、Modifyボタンを押し、Installation locationsのタブを表示して、ダウンロードキャッシュを有効にする必要があるようです。





その後、MoreボタンからRepairをクリックして修復インストールを行います。



さらに、修復インストールが完了したあと、

C:\ProgramData\Microsoft\VisualStudio\Packages\Microsoft.VisualStudio.AspNetDiagnosticPack.Msi,version=16.0.12311.10635

のフォルダ内にある、AspNetDiagnosticPack.msi のファイルを右クリックして「Repair」を実行します。


以上の手順で無事復活しました。


パッチが出るまではおそらく同じ問題にぶつかる人がいると思いますので、メモしておきます。




追記:

今日から2019をメインで仕事に使っていますが、この記事に書いた問題以外は特に問題もなく今のところ普通に使えています。

2017と比べて特に軽くなった感じはしないので何が良くなったのかほとんど分からないのですが(笑)。








 

2019年3月22日

React/TypeScriptでリバーシゲームを作る (5) - アニメーション

前回でようやくそれなりに強い思考ルーチンを実装することが出来たので、今回は仕上げとして石を裏返すときのアニメーションと、画面遷移時のアニメーションを実装します。


こちらを開くと実際に遊べます。
https://reversi-d1kqojbar.now.sh/


現時点のソースコードはこちらにあります。



1. 石をひっくり返す時のアニメーション


まず、石を置いた時には StoreクラスのsetStone()メソッドが呼ばれるようになっているので、その中で20msの間隔を空けて20回の「EV_BOARD_FLIPPING」イベントを発生するようにしました。

このイベントをBoardコンポーネントで受け取って、各セルを描画する際に、「もしひっくり返しアニメーションの実行中で、かつひっくり返し対象のセルであれば、Cellコンポーネントにflippingプロパティを通してその旨を伝える」ということを行っています。

Cellコンポーネントでは、flippingプロパティがnullでない場合は、

 flipping.count / flipping.total 

で現在のアニメーションの進行率が分かるので、それに応じて円を描画するときの横幅を変化させています。

さらに、アニメーションの進行率が 50% を越えた時点で石の色を反転させるようになっています。

これで石をひっくり返すアニメーションは上手く行きました。


石をひっくり返すアニメーション

---



ちなみに、CSSトランジションを使えば自分で石の横幅を変化させなくても、開始状態と終了状態を指定するだけでブラウザ側でアニメーションさせることが出来ます。その方法を使った方がパフォーマンス的には良かったかも知れません。




2. 画面遷移時のアニメーション


今回のアプリケーションでは、

 タイトル画面 → 設定画面 → ゲーム画面 

という流れで画面が切り替わるようになっています。この切り替えのタイミングで、次に進む場合は右から左へ、前の画面に戻る場合は左から右へとアニメーションする処理を入れました。

タイトル画面 → 設定画面 → ゲーム画面の遷移アニメーション

---

この部分は、CSSトランジションを使っています。

Reactでは 「React Transition Group」というライブラリを使うのが半ば公式に推奨されているみたいです。

https://github.com/reactjs/react-transition-group

これを使うのも悪くないのですが、今回は勉強のために全て自前で実装してみました。その結果、かなり試行錯誤して時間がかかりましたが、分かってみればそんなに難しくはないので、これぐらいの画面遷移であれば自前でやってしまうのもありだと思います。

基本的には、CSSで表示状態のスタイルと非表示状態のスタイルを定義しておいて、各画面のコンポーネントでCSSクラスを切り替えるだけです。

非表示状態の画面は透明(opacity:0)でかつ表示位置が画面の範囲外になるようにしていますが、それだけだとドキュメント全体のサイズが、表示されていない部分まで含んで認識されてしまって、余計な横スクロールが出来てしまう状態になったので、そこは一工夫が必要でした。

これに対処するために、一段上のAppコンポーネントのスタイルで、

.App {
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}

とすることで、画面外の部分はドキュメント全体のサイズに影響をおよぼさないようになり、余計な横スクロールを抑止することが出来ました。



3. スマホ対応


AndroidやiOSの実機で動作確認していると、色々と気になる点が出てきました。

スマホ向けにPWAなどでゲーム的なアプリケーションを作る場合には常に出てくる問題だと思いますが、以下の3つの問題があります。

1. ダブルタップ問題
2. ピンチズーム問題
3. Pull to Refresh問題


これらになんとか対処するために色々と調べて、以下の変更を行いました。どれがどの問題への対策だったかよく覚えていないのですが、とりあえず下記の対策をしておけば良いのではないかと思います。


htmlタグに属性を追加

style="overflow-y: hidden;"


viewportメタタグの設定

meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no,shrink-to-fit=no"

モバイルアプリ用メタタグの追加

meta name="apple-mobile-web-app-capable" content="yes"
meta name="mobile-web-app-capable" content="yes"



これで少なくとも手元のAndroid端末(Android 9.0)ではほぼ完璧になりました。

Chromeブラウザでページを開いてから、「ホーム画面に追加」を選んでホーム画面から起動すると、ブラウザのアドレスバーも無くなり、ダブルタップしようが上下にスワイプしようがびくともしません。ほぼネイティブアプリの感覚ですね。

手元のiOS端末(iPhone 5s / iOS 12.1.3)では、Pull to Refresh問題は解消しましたが、ダブルタップやピンチズームは反応しなかったり何かのタイミングで急に出来てしまったり、よくわからない感じです。(笑)


4. 終わりに


さて、ここまでで今回の「React/TypeScriptでリバーシゲームを作る」シリーズは無事終了です! 10月から3月まで、ほぼ半年もかかってしまいました。その間にGoogle発のクロスプラットフォーム開発環境であるFlutterが正式リリースされて、最近はかなり人気も高まってきているようです。


今度はFlutterでまたリバーシゲームを作ってみようかなあと思っているところです。




React/TypeScriptでリバーシゲームを作る

(1) - ボードの描画と石の配置
https://blog.makotoishida.com/2018/10/reacttypescript.html

(2) - ゲームロジック
https://blog.makotoishida.com/2018/11/reacttypescript-2.html

(3) - 思考ルーチンその1
https://blog.makotoishida.com/2019/01/reacttypescript-3-1.html

(4) - 思考ルーチンその2
https://blog.makotoishida.com/2019/03/reacttypescript-3-2.html

(5) - アニメーション
https://blog.makotoishida.com/2019/03/reacttypescript-5.html






 

2019年3月7日

React/TypeScriptでリバーシゲームを作る (4) - 思考ルーチンその2

前回でコンピュータの思考ルーチンの枠組みは出来ましたが、まだ単純なルールで動いているだけなので全く強くありませんでした。

今回は、そこそこ強い思考ルーチンを作ることに挑戦してみました。

出来たソースコードはこちらにあります。
https://github.com/mikehibm/reversi-react/tree/blog-4


動いているものはこんな感じになりました。
---

---

下のURLで実際に動かせますのでぜひ試して見て下さい。

https://reversi-7kj2lcsw4.now.sh/


CPUのレベルは1から3まであります。レベル1は1手先、レベル2は2手先、レベル3は3手先まで打てる可能性のある場所を全て調べて、最も有利になりそうな場所に打つようになっています。

思考ルーチンの内容は長くなるので省略しますが、とても面白いテーマです。興味のある方には、次の本を強くおすすめしておきます。これからリバーシを自分で作ってみようと思う人には本当に役に立つ情報が詰まっています。
---

---

今回は、上の本では「用いないほうが良い」と書かれている「得点テーブル」による評価を使ってしまいましたが、それに加えて「確定石」の数による評価を組み合わせて実装したところ、意外とまあまあ強い思考ルーチンになったような気がしています。


さて、このアプリケーションではコンピュータの思考ルーチンを Web Worker を使って別スレッドで実行するようになっています。と言うと簡単に聞こえますが、実際にはかなり試行錯誤と苦労の連続でした。


なぜ苦労したかと言うと、それなりに複雑な処理をWebWorkerで実行しようとするとやはり TypeScript を使いたいし、複数のWorker間で共通に使える関数はモジュール化して import 出来るようにしたかったからです。


① WebWorkerもTypeScriptで書く、かつimport文を使えるようにする

②  Create React App で作成されたプロジェクトをejectせずに(Webpackの設定を変えずに)これを実現する


この①と②の目標を達成するためにいろいろと試した結果、前回の記事で使った「Workerの関数をtoString()で文字列化した上でBlobとして読み込んでからWorkerスレッドを生成する」という方法ではなく、シンプルな

  const worker = new Worker('my-worker.js');

という形式で単にpublicフォルダに置いたJSファイルを指定して読み込む方法を使うことにしました。

その上で、WebWorker関連のTypeScriptファイルだけをアプリケーション本体とは切り離して独自にトランスパイルする方法を考えました。


ただ、共通部分をモジュール化して import/export を使うというのは、結局WebWorkerとの組み合わせではいい感じで正しく動かすための設定方法を見つけることが出来ませんでした。(tscでトランスパイルするのではなくwebpack/babelとworker-loaderプラグインなどを使えばなんとかなるのかも知れません。詳細は末尾の参考URL参照)


その代わりに、

  importScripts('インポートされるJSファイル名') 

という記法はWebWorkerの中で問題なく使えたので、これを利用することにしました。


プロジェクトのルートに tsconfig.json がありますが、それとは別に「tsconfig.worker.json」ファイルを作成しました。

---

---


  "module": "none",

とすることでモジュールシステムを使わないようにしている点と、

  "outDir": "public"

でトランスパイル後のJSファイルを直接publicフォルダに保存している点に注目です。


これで、

  tsc -p tsconfig.worker.json

を実行すると
  public/reversi.worker.js
  public/players/cpu1.worker.js
  public/players/cpu2.worker.js
  public/players/cpu3.worker.js

という4つのJSファイルが出来るようになります。


cpu1〜3.worker.jsの先頭では、

  importScripts('../reversi.worker.js');

とすることで共通部分である reversi.worker.js を読み込んでいます。


あとは、アプリ内でWorkerを生成する必要があるときに

  new Worker('players/cpu1.worker.js');

のような感じで読み込めば良いということになります。




参考URL:
Workerを駆使するためのプロジェクト構成 with webpack - Qiita
https://qiita.com/_likr/items/d382dc120a942ba4c6fe
4パターンのWebWorker生成方法とインラインワーカーの技法 - Qiita
https://qiita.com/mohayonao/items/872166cf364e007cf83d
Two example projects which use WebWorker in TypeScript + Webpack environment.
https://github.com/Qwaz/webworker-with-typescript 





React/TypeScriptでリバーシゲームを作る


(1) - ボードの描画と石の配置
https://blog.makotoishida.com/2018/10/reacttypescript.html

(2) - ゲームロジック
https://blog.makotoishida.com/2018/11/reacttypescript-2.html

(3) - 思考ルーチンその1
https://blog.makotoishida.com/2019/01/reacttypescript-3-1.html

(4) - 思考ルーチンその2
https://blog.makotoishida.com/2019/03/reacttypescript-3-2.html

(5) - アニメーション
https://blog.makotoishida.com/2019/03/reacttypescript-5.html






.

2019年2月16日

React StaticでGoogle Analyticsを使うには

React StaticでSPAアプリケーションを作ったときに、Google Analyticsでアクセス解析をするにはどうすればよいか、というメモです。

Reactだと react-ga というライブラリがよく使われているようです。これを使うとGoogle Analyticsへのトラッキング情報の送信が楽になるようですが、ブラウザ上でのページ遷移のタイミングは自分で検知して送信するようにコードを書かなければなりません。

React Routerを使う場合のサンプルがGitHubのdemoフォルダ内にありました。

https://github.com/react-ga/react-ga/blob/master/demo/app/withTracker.jsx

これを見ると、withTrackerというHigher Orderコンポーネントを作ってlocationの変更を検知してGAに送信するようになっています。


今回はReact Staticを使って作ったサイトだったので、ルーティングの仕組みが少し違ってこのサンプルコードをそのまま使うことが出来ず、少し手を加えたものを作って一応動くようにはなったのですが、なんとなくしっくり来ませんでした。


そこであらためてGoogle Analyticsのドキュメントを読み返してみると、下のような記述を見つけました。

https://developers.google.com/analytics/devguides/collection/analyticsjs/single-page-applications
--
デフォルトの JavaScript トラッキング スニペットは、ユーザーが新しいページを読み込むたびに実行されるため、従来のウェブサイトでは正常に動作しますが、SPA の場合、サイトで新しいページを読み込むときに、ページ全体を読み込むのではなくコンテンツを動的に読み込むため、analytics.js スニペット コードが実行されるのは一度だけとなります。つまり、以降のページビュー(仮想ページビュー)は、新しいコンテンツが読み込まれるときに手動でトラッキングする必要があります。



--

青枠の注釈部分が特に重要です。

SPA用に、既にGoogleからurlChangeTrackerというプラグインが提供されているのです。これを使うことで、自分でページ遷移のタイミングを拾ってトラッキング情報を送信するという処理を書く必要が無くなります。

つまり、やるべきことは以下の通りになります。

1. 初回表示時にanalytics.jsを読み込んで初期化 & 初回トラッキング情報を送信。
2. urlChangeTrackerを読み込む。(以降のページ遷移は自動的に送信される。)

結局、「react-gaを使わずに最初から自分で書いた方がシンプルになるのでは?」と思ったので一から書き直しました

--

--

render()メソッド内で autotrack.custom.js というスクリプトファイルを読み込んでいますが、これは urlChangeTrackerのドキュメントにしたがって必要なプラグインだけをカスタムビルドして作成したものを public フォルダ内に配置したものです。

このGoogleAnalyticsコンポーネントをApp.js内で読み込んで下のように使うことで、React Staticで作ったサイトでGoogle Analyticsがちゃんと機能するようになりました。



最後に、GoogleAnalyticsコンポーネント内で参照している gaID というプロパティですが、これは React Static の withSiteData というHoCを使って自動的に挿入されます。実際の値の定義は下記のように static.config.js ファイル内で行っています。
--

--


2019年1月13日

React/TypeScriptでリバーシゲームを作る (3) - 思考ルーチンその1

前回はリバーシのルールを実装して、人対人の対戦が出来るようになりました。

今回は、一人でも遊べるようにコンピュータの思考ルーチンを実装します。


動くものはこちらです。
https://reversi-992nmioqt.now.sh/



タイトル画面でレベル1〜3が選べるようになっています。
(現段階ではどれを選んでも実際には難易度は同じです。)

ゲーム画面は前回と同じですが、コンピュータの手番になると自動的に思考ルーチンが呼び出され、
結果が出るとその座標に石を配置します。




現時点でのソースコードはこちらにあります。
https://github.com/mikehibm/reversi-react/tree/blog-3


さて、コンピュータとの対戦を実現するにあたって工夫する必要があるのは、「思考ルーチンを別スレッドで動かして、UIをブロックしないようにする」ことです。


通常ブラウザ上で動くJavaScriptアプリケーションでは一つのスレッドしか使えないので、時間がかかってCPUパワーを消費する処理を行うとその間UIが固まったりカクカクした感じになったりして操作性が悪くなります。

これを防ぐために、「Web Workers API」を使います。


ReactでWeb Workersを使う方法は、下の記事に書いたとおりです。

Create React AppでWeb Workerを使うには

この記事で調べた、「WorkerのJSファイルをBlobとして読み込んでからWorkerスレッドを生成する」という方法を使うことにしました。


ひとまず基本的な仕組みを実装することに主眼を置いたので、思考ルーチンの内容は単に「配置可能な座標のなかからランダムに選ぶ」だけの動作になっています。この処理だと実際には一瞬で終わってしまうのでWorkerスレッドを使う意味は全くありません。

Workerスレッドを使う意味があるような、もっとヘビーにCPUを使う思考ルーチンへの改良については、次回以降の記事で書くことにします。


playersフォルダ内のcomputerPlayer1.tsを見ると、thinkProc() という関数があり、そこに思考ルーチンの実態が入っているのが分かります。
---



この thinkProc関数を createWorker というユーティリティ関数に渡すことで thinkWorkerという名前のWorkerスレッドを作成しています。
const thinkWorker = createWorker(thinkProc);


対戦中にコンピュータの番が来たときには、

const result = await thinkWorker.execute({ board: { cells: board.cells } });

のように execute()メソッドを呼んで思考ルーチンを起動しています。このexecute()メソッドというのは、JavaScript標準のWorkerクラスを継承して作成した独自クラスで定義したメソッドで、 Workerクラスの postMessage()を呼んだ後のイベントハンドリングを抽象化したものになっています。


思考ルーチンの処理が終わると、戻り値のresult変数にはコンピュータが選択した座標が入っているので、後は人間がクリックした時と同じように store.setStone(result) を呼んでその座標を渡します。そうすると store内部で盤面の状態が適宜更新されてイベントが発行されるので、それを受けて画面の表示が自動的に更新されることになります。



ここまででコンピュータとの対戦機能を実現するための基本的な仕組みを実装することが出来ました。次回はそれなりに強い思考ルーチンを作ることにチャレンジしたいと思います!




React/TypeScriptでリバーシゲームを作る


(1) - ボードの描画と石の配置
https://blog.makotoishida.com/2018/10/reacttypescript.html

(2) - ゲームロジック
https://blog.makotoishida.com/2018/11/reacttypescript-2.html

(3) - 思考ルーチンその1
https://blog.makotoishida.com/2019/01/reacttypescript-3-1.html

(4) - 思考ルーチンその2
https://blog.makotoishida.com/2019/03/reacttypescript-3-2.html

(5) - アニメーション
https://blog.makotoishida.com/2019/03/reacttypescript-5.html