ラベル JavaScript の投稿を表示しています。 すべての投稿を表示
ラベル JavaScript の投稿を表示しています。 すべての投稿を表示

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年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







 

2018年11月15日

Create React AppでWeb Workerを使うには

「Web Worker」を使うと、ブラウザ上のJavascriptでも複数のスレッドを使うことが出来るようになります。


Web Worker を使用する - Web API | MDN https://developer.mozilla.org/ja/docs/Web/API/Web_Workers_API/Using_web_workers#Web_Workers_API


これによって、CPU負荷の高い計算処理などを別スレッドで実行することで、画面の反応が鈍くなったりするのを防ぐことが出来ます。


今回は、Create React Appで作成したプロジェクトでWeb Workerを使う方法を調査しました。


1. publicフォルダにWorkerのJSファイルを配置して読み込む。


⇒ 最も基本的で単純な方法です。publicフォルダに配置したファイルはそのままデプロイされるので下の方法で素直に読み込んで使うことが出来ます。

const myWorker = new Worker("worker.js");
myWorker.postMessage("hoge");


短所としては、TypeScriptやES2017などを使っている場合は別途自分でビルドしてpublicフォルダに配置する必要があることが挙げられます。


2. ejectしてからWebPackの設定に worker-loader または worker-plugin を追加する。


⇒ worker-loaderを使うと、別スレッドで動かしたい処理をModuleとして作成しておいて、使いたい箇所でそれをimportしてnewするだけでWeb Workerとして動作するようになります。コード例は、下のようになります。

import HelloWorker from './hello.worker.js';
const helloWorker = new HelloWorker();
helloWorker.postMessage('hoge');

詳細はこちら。
Add support for WebWorker with worker-loader (#3660) by iansu · Pull Request #3934 · facebook/create-react-app 

worker-plugin を使う場合はこんな感じ。

const worker = new Worker('./worker', { type: 'module' });
worker.postMessage('hoge');

詳細はこちら。
Add support for WebWorker with worker-plugin by Krivega · Pull Request #5464 · facebook/create-react-app  




3. ejectせずに react-app-rewired を使ってWebPackの設定に worker-loader または worker-plugin を追加する。


⇒ 下のブログ記事にejectせずに上の2と同じ事をする方法が書かれていました。

How to use web workers with react-create-app and not ejecting in the attempt

ただ残念ながら、react-app-rewiredは最近のVersionには非対応なのでこの方法は現在は使えないようです。


4. WorkerのJSファイルをBlobとして読み込んでからWorkerスレッドを生成する。


⇒ 下のissueへのコメントで紹介されている方法です。

Is it possible to use load webworkers? · Issue #1277 · facebook/create-react-app

関数をtoString()で文字列化して、さらにそれをBlobにします。こうすると、WorkerのコンストラクタにObjectURLとして渡してWorkerインスタンスを生成出来るようです。

let code = worker.toString();
code = code.substring(code.indexOf("{")+1, code.lastIndexOf("}"));
const blob = new Blob([code], {type: "application/javascript"});
const worker = new Worker(URL.createObjectURL(blob));

少々トリッキーですが、試してみたところ少なくともMacのChrome/Safari/FireFoxでは問題なく動いています。

この方法なら、WebPackの設定を変更する必要がありません。また上記1のようにWorkerのJSファイルだけ自前でビルド・配置したりする必要もありません。

具体的なコード例は後述します。


5. 公式にサポートされるまで待つ。


⇒ 上の2で挙げた方法がPull Requestとして上がっており公式にも検討されているみたいですが、どうやらブラウザ互換性の問題(?)があるようで2018年11月時点ではまだマージされるには至っていません。将来的には公式にサポートされる可能性はあるので、気には止めておいた方が良いかも知れません。

Add support for WebWorker with worker-loader (#3660) by iansu · Pull Request #3934 · facebook/create-react-app 

Add support for WebWorker with worker-plugin by Krivega · Pull Request #5464 · facebook/create-react-app 



今回は上記4の方法を試してみたので、以下に紹介しておきます。

WorkerのJSファイルをBlobとして読み込んでからWorkerスレッドを生成するサンプル


index.jsと同じフォルダにcreateWorker.js, myWorker.jsの2つのファイルを作成します。

createWorker.js

---

myWorker.js

---


createWorkerとmyWorkerをimportして、下のようにすることでWorkerスレッドが使用可能になります。

import createWorker from './createWorker';
import myWorkerFunc from './myWorker';
const myWorker = createWorker(myWorkerFunc);


index.js

---









2018年11月4日

React/TypeScriptでリバーシゲームを作る (2) - ゲームロジック

前回はボード上に石を配置するまでを実装しましたが、今回はさらにリバーシのルールを実装して実際に遊べるようにしたいと思います。


実際に動いているものはこちらで試すことが出来ます。

https://reversi-ocmiwdgcdj.now.sh



ゲームのルールを実装するには、大まかに分けて次の4つの処理が必要になります。


  1. 「次に石を配置可能な場所」の判定と表示
  2. 挟まれた石を裏返す
  3. 石を置ける場所が無い場合の「パス」処理
  4. ゲーム終了判定と勝敗の表示


1. 「次に石を配置可能な場所」の判定と表示


各手番が始まったときに、ボード上の全てのCellをループして次の処理を行います。


  • 現在のCellからタテ・ヨコ・ナナメ全ての方向に向かってひとつずつ次のCellの色を確認する。
    • 相手の石が置かれていたら、一時的な配列(arr)にそのCellの座標をプッシュして、さらにその方向の次のCellを確認。
    • 自分の石が置かれていたら、その方向への探索を終了。
    • 空白なら、その方向への探索を終了。
    • 座標がボードの範囲外に出た場合は、その方向への探索を終了。
  • 上の処理で最後に自分の石が見つかった場合で、かつ配列arrに要素が含まれている場合は、「挟める対象の相手の石が見つかった」ということなので、そのCellを「自分の石を配置可能なCell」としてマークしておく。(cell.placeableプロパティにtrueをセットする。)


Cellコンポーネントでは、対応するCellオブジェクトのplaceableプロパティが属性として渡されてくるので、その値がtrueの場合は、小さな黄色の円をSVGのcircleタグを使って表示するようになっています。


2. 挟まれた石を裏返す


プレーヤーがCellをクリックしたときには、CellコンポーネントのhandleClick()メソッドが実行され、さらにstoreのsetStone()メソッドにクリックされたCellの座標が渡されます。

store側では、Cellのplaceableプロパティがtrueでない場合は、そこには石を配置出来ないので何もせずにreturnしています。

そうでない場合は、クリックされた場所およびそこを起点として裏返し対象になるCellのcolorプロパティを全てプレーヤーの石と同じ色に変更します。

「裏返し対象のCell」というのは、あらかじめ上の「次に石を配置可能な場所」の判定処理の中で見つかったCellの座標一覧をcell.turnableCellsというプロパティに格納してあるので、それをループすることで取得できます。

裏返し対象のCellのcolorプロパティを全て変更する処理が終わった後、getNextTurn()メソッドを呼んで次のプレーヤーに手番を交替しています。

最後に'board_changed'イベントを発行して、各コンポーネントの描画を更新しています。



3. 石を置ける場所が無い場合の「パス」処理


現在の手番で石を置ける場所が見つからない場合は、ボードの placeableCount プロパティが 0 になります。

その場合は、ひとまず alert()でメッセージを表示した後、storeのskipTurn()メソッドを呼んで手番を「パス」します。

この処理はゲームの進行に関わる処理なので、Gameコンポーネント内で行っています。

skipTurn()メソッドの中では、ボードの状態を変更せずに、単に現在の手番を示す turn プロパティを更新して'board_changed'イベントを発行しています。



4. ゲーム終了判定と勝敗の表示


Gameコンポーネントでstoreから'board_changed'イベントを受け取った時に、finishedプロパティがtrueであればゲーム終了なのでその旨をalert()で表示します。

finishedプロパティがtrueになる条件は、以下の通りです。

  • 空白のCellが無くなった。
  • 黒または白のどちらかが全滅した。
  • 両者とも置く場所が無くなった。


ボードの状態を更新する際にこのいずれかに当てはまるかどうかを毎回チェックしてfinishedプロパティの値をセットしています。



以上でリバーシのルールを実装して実際に遊べる状態にまで持って行くことが出来ました。

次回は、コンピュータの思考ルーチンを作成してコンピュータとの対戦機能を追加したいと思います!


今回の時点でのソースコードはこちら:

https://github.com/mikehibm/reversi-react/tree/blog-2




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










 

2018年10月20日

React/TypeScriptでリバーシゲームを作る (1) - ボードの描画と石の配置

実は2011年に「Androidでオセロゲームを作る」というのをやった事があります。5回のブログ記事に分けて少しずつ作り込んで行ったのですが、このシリーズが今でも結構アクセスされているみたいです。

これを読み返しながら、ふと「今だったら、ReactでWeb版を作っておけばネイティブアプリ化も結構簡単に出来るし色々と応用が効くのでは?」と思い付きました。

そこで、作り始めて見ました。

現時点で動作しているものはこちらで試せます。
https://reversi-mngzdihdyn.now.sh/

タイトル画面


ゲーム画面

ソースコードはこちら

https://github.com/mikehibm/reversi-react/tree/blog-1


まだ画面をクリックして石を置くことが出来るようになっただけでゲームのルールが何も実装されていません。

しかし、リバーシゲーム作りは楽しいですね!


以下、ここまでに書いたコードの大まかな説明です。

コンポーネント階層


今のところ下のような階層になっています。

  • App (アプリケーションの大枠)
    • Menu (メニュー画面)
    • Game (ゲーム画面)
      • Board (ボードの背景や枠線などを描画するコンポーネント)
        • Cell (一つのセルを描画するコンポーネント)



状態管理


アプリケーションの状態は store.ts ファイルで一括管理しています。

ここでエクスポートしている Store クラスが EventEmitter を継承しており、状態に変更があったときにイベントを発生(emit)することでそれを各コンポーネントに通知して、各コンポーネントが画面を更新するという流れになっています。

例えば、メニュー画面でスタートボタンが押されたときには、setPage()メソッドが呼ばれて Store クラス内で保持している page 変数の値が変更されるので、その際に 'page_changed' というイベントを発生させています。

このイベントは App コンポーネントが監視していて、イベント発生時に最新の page の値を受け取って メニュー画面を表示するのかゲーム画面を表示するのかを切り替えています。





ボードとセルの描画


Boardコンポーネントでは、SVGを使ってボードの緑色の背景や縦と横の罫線などを描画しています。

また重要なのは、Cellコンポーネントの配列を作成してそれを子コンポーネントとして描画していることです。ボード上には 8 x 8 で64個のCellコンポーネントが配置されることになります。

Cellコンポーネントでは、propsとして渡された座標値にしたがってSVGのRectangleを描画します。このRectangleがマウスのクリックイベントを受け取る入り口になります。

また、propsのひとつとしてcolorが渡されますが、これはそのセルが空白なのか、黒の石が配置されているのか、または白の石が配置されているのか、を示します。

Cellコンポーネントではその値によって黒または白の円を描画する処理を行っています。

あるセル上でマウスがクリックされた時は、次の流れで最終的にセルに石が描画されるようになっています。


  1. handleClick()メソッドが呼ばれる。
  2. Storeクラスの setStone()メソッドが呼ばれる。
  3. クリックされた場所に石が無ければ、その場所の colorプロパティを現在の手番の色に変更する。
  4. Storeクラスが'board_changed'イベントを発生する。
  5. Boardコンポーネントがイベントを受け取り、ボード全体を再描画する。
  6. クリックされた場所に対応するCellコンポーネントが石を描画する。


こんな感じで、ひとまずボードの描画と石の配置までは出来るようになりました。

次回は、リバーシゲームとしてのルールを実装して実際に遊べるようになるまでを作ってみたいと思います!





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









2018年8月10日

ReactでCSVファイルを読み込んでクライアント側で帳票を出力する

やりたかった事


あるWebサービスからCSV形式でデータをエクスポート出来るのですが、そのデータからちょっとした帳票を印刷したいという要望がありました。

そこでなるべく手っ取り早く、ブラウザだけで動くものが出来ないかと考えて作ってみたのが今回のプログラムです。

大まかな流れは、
  1. CSVファイルを指定すると、ブラウザ上のJavaScriptでその内容を読み込む。
  2. 読み込んだ内容からHTMLで帳票を生成して表示する。
  3. ブラウザの印刷機能を使って手動で印刷またはPDFとして保存する。
という感じになります。


CSVファイルの例







作成したい帳票


実際に動いているもの

https://mikehibm.github.io/react-csv-example/




1. プロジェクトを作成する


プロジェクトは前回のエントリーと同様に Create React App + TypeScriptで作ることにします。
npm install -g create-react-app
create-react-app my-app --scripts-version=react-scripts-ts
cd my-app/
npm start

FileSelect.tsx と Report.tsx というファイルを新規作成して、App.tsx からこれらを呼び出して使うようにします。

最初は FileSelect コンポーネントを表示してCSVファイルの選択を行い、ボタンがクリックされたらファイルの内容を読み取って、Report コンポーネントに渡します。




Report コンポーネントが表示されている時は FileSelect コンポーネントは非表示になるようにします。




2. JavaScriptでCSVファイルの内容を読み込む


HTML5のFile APIを使います。FileReaderでの読み込みは非同期処理なので、処理をラップしてPromiseを返す関数を作りました。







3. ファイルの内容をパースしてオブジェクトの配列に変換する


上の readFileAsText() からファイル全体の内容が文字列として返ってくるので、それをパースしてオブジェクトの配列に変換します。

具体的には、まず改行文字で区切って行単位の配列に分け、さらにその各行について「,」で区切って列単位の配列に分けます。つまり最終的には string[][] (文字列の配列の配列)になります。




ここでは簡単に改行文字(\n)と「,」で区切っているだけですが、実際には改行文字が違う、値に「,」が含まれている、などさまざまなケースがあり得るのでCSVのパース処理はちゃんとやろうとすると実は結構大変です。

なので必要であれば下の記事で紹介されている csv-parser などを使った方が良いかもしれません。

ブラウザ上でCSVファイルをパースする
https://qiita.com/ledsun/items/e38ee0dff8f26bf8d930




その後、文字列の配列(の配列)から、今度は帳票を生成する時に扱いやすいように、WorkItemというクラスのインスタンスの配列に変換します。

この時に、文字列型から日付型への変換や、CSVファイルに無い項目の値(duration)の計算なども同時に行っています。





4. HTMLで帳票を生成する


WorkItemオブジェクトの配列を受け取って、tableタグで表形式のHTMLに変換します。

本当は帳票のタイトル、見出し、明細行などをそれぞれ別のコンポーネントに分けた方が良いのだと思いますが、今回はあまり時間がなかったので1コンポーネントで作ってしまいました。





あとは画面に表示された帳票をブラウザの印刷機能で印刷するだけです。デフォルトではおそらく余計なヘッダーやフッター(ページタイトルやURL)も印刷されてしまいますが、印刷時の設定画面でこれらをオフにしておけばきれいに出力されるはずです。


今回作ったアプリの全ソースコードは下記にあります。

https://github.com/mikehibm/react-csv-example







2018年8月8日

Create React App + TypeScript で最初につまずいたこと

ちょっとしたサンプルプログラム的なものを Create React App + TypeScript で作ろうと思ったのですが、思わぬところでいきなり詰まってしまいました。


Create React App(以下CRA)で TypeScript と言えば、

https://github.com/wmonk/create-react-app-typescript

が定番だと思います。


さて、下の通りにプロジェクトを作成して実行しました。

npm install -g create-react-app
create-react-app my-app --scripts-version=react-scripts-ts
cd my-app/
npm start


もちろんここまでは全く問題なし。早速VS Codeでプロジェクトのフォルダを開いて、アプリケーションを作り始めました。



ところが、あるタイミングでふと

console.log("hogehoge");

を入れて実行してみようと思ったところ、いきなりコンパイルエラーになってしまいました。


「Calls to 'console.log' are not allowed.」というエラーですが、これにはちょっと驚かされました。console.logを入れただけでアプリケーションが動かなくなるなんて。。。


これですが、デフォルトで設定されているTSLintのルールがめちゃくちゃ厳格なものになっているのが原因なようです。


厳格すぎてこれでは使いづらいという声も多く、GitHubのIssuesでも議論になっているようですね。


オリジナルのCRA作者のDan Abramov氏からも、「この厳しいルールはCRAを作った時の思想(初心者でもすぐに動くものを作り始められる。本当にクリティカルなもの以外はワーニングのみでエラーにはしない。)と合わないから、ゆるくした方が良いんじゃない?」 という趣旨のIssueをわざわざ上げられているぐらいです。


create-react-app-typescript リポジトリ作者のWill Monk氏によると、「最初は自分が職場で使っているルールをコピーして使っていたのだけれど、後で一般的に推奨されているプリセット("tslint:recommended", "tslint-react"など)を使うように変更した」との事。「それでもまだルールが厳しすぎるなら、誰かがオリジナルのCRAと同等のゆるいルールの設定を作ってPRしてくれたら喜んでそれをマージするよ。」とも言っているので、彼自身は特にどうしても厳しいルールを他の人に強制する気は無いようです。


その後このIssue上で議論は進んでいるようですが、このブログ執筆時点の最新版ではまだルールの変更には至っていないようです。


さて、なにはともあれとりあえず console.log を使えるようにする方法ですが、上のIssueでも言及されているのですが、Microsoftによる下記のページにわかりやすくはっきりと記載されていました。

https://github.com/Microsoft/TypeScript-React-Starter


プロジェクトのルートフォルダにある tslint.json ファイルを下のように書き換えればOKです。(赤字を削除、青字を追加。)

{
-  "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"],
+  "extends": [],
+  "defaultSeverity": "warning",

"linterOptions": {
     "exclude": [
       "config/**/*.js",
       "node_modules/**/*.ts"
     ]
   }
 }

これで問題なく開発が続けられるようになりました!






 

2018年5月1日

JavaScriptで "B" + ["a", "a"].join("aaa" - 1) がBaNaNaになる理由

Ethereum開発者のVitalik氏がこんなツイートをしていたのを見ました。


試しにChromeのDeveloper Toolsを開いて実行して見ると、確かに「BaNaNa」と表示されました。
"B" + ["a", "a"].join("aaa" - 1)
"BaNaNa"

不思議に思ったので少しずつ変えながら何パターンか試してみます。

"B" + ["a", "a"].join("aaa") → "Baaaaa"
"B" + ["a", "a"].join()  → "Ba,a"
"B" + ["a", "a"].join("")  → "Baa"
"B" + ["a", "a"].join("" - 1)  → "Ba-1a"
"B" + ["a", "a"].join(1)  → "Ba1a"
"B" + ["a", "a"].join("x" - 1)  → "BaNaNa"

なるほど、分かってきました。

"aaa" - 1 の部分が「NaN」として出力されて、それがArray.join()の引数になるので
B + a + NaN + a
で 「BaNaNa」というわけですね。

なるほど!

 

2018年3月1日

TypeScriptを使うならstrictNullChecksオプションが便利

TypeScript の strictNullChecks オプションというものを知ったのでメモしておきます。

tsconfig.json の compilerOptions に

"strictNullChecks": true

を追加しておくと便利。

これをすることで、string や number などに null や undefined を代入することが出来なくなる。

let name: string;
name = null;                ← ここでコンパイルエラー

let count: number;
count = undefined;      ← ここでコンパイルエラー


null や undefined を代入したい場合は、型指定時に明示しておけばOK。

let name: string | null;
name = null;                ← これはOK

let count: number | null | undefined;
count = undefined;      ← これはOK


「ユニオン型」というらしいですね。
http://d.hatena.ne.jp/m-hiyama/20180117/1516157225

色々なシチュエーションで、「この変数の値は null/undefined になる可能性がある(もしくは無い)」という事を明示出来るのでnullチェック忘れや過剰なnullチェックを減らす事が出来ます。




 


2017年6月19日

Angularでサーバーから取得したPDFを表示する


Angularでサーバーから取得したPDFを表示するサンプルを作ってみました。


実際に動くものはこちら

全ソースコードはこちら。



サーバーからPDFファイルを取得する部分



サンプルではGitHub Pagesからスタティックなファイルをgetしていますが、Web Apiから動的に生成されたファイルを取って来る場合でも全く同じです。



受け取ったPDFファイルをクライアント側で表示する部分




URL.createObjectURL(blob) でblobからデータURLを生成して、それをHTML側で object タグの data 属性にバインドして表示しています。

ただそれだけだとAngularのセキュリティ制限でエラーになるので、DomSanitizerのbypassSecurityTrustResourceUrl()というメソッドを呼ぶ必要がありました。


あ、それからこのサンプルは現時点ではIEでは上手く動かない様です。

ChromeとFirefoxでは問題無く動作しました。

MacのSafariではobjectタグでの表示はされましたが、Downloadのリンクをクリックしても別タブで表示されるだけでダウンロードはされませんでした。

ブラウザ objectタグでの表示 aタグでのダウンロード
Chrome
Firefox
Safari
Edge ×
IE11 × ×











2017年5月18日

Node, express, nginx で動くWebサイトへのファイルアップロードでサイズ超過エラーが出る時の対策

Node/Express.js で作ったWebアプリケーションをテストしていて、CSVファイルのアップロード時に一定のファイルサイズを超えると「413 Request Entity Too Large」というエラーになる事に気付きました。


調べた結果、

  1. ExpressのbodyParserミドルウェア
  2. MultiPartフォームデータの処理を簡単にしてくれるミドルウェアのMulter
  3. そしてリバースプロキシとして使っているNginx 

の3箇所で許可するファイルサイズの最大値を設定する必要があることが分かりました。


1. Expressの設定


app.use(bodyParser.json({ limit: '20mb' }));
app.use(bodyParser.urlencoded({ extended: true, limit: '20mb' }));


2. Multerの設定


const upload = multer({
  limits: {
    fileSize: 1024 * 1024 * 20, // 最大20MBまで許可
  }
});



3. Nginxの設定


location / {
proxy_pass http://127.0.0.1:3000;
... (略) ...
client_max_body_size 20M;
}


最初1と2の設定を付けたのに上手く行かなくて、3の設定が必要な事に気付くまでに時間がかかってしまったので、メモしておきます。







 

2016年8月10日

Visual Studio Code で .js.map など特定のファイルを非表示にする

VS Codeで TypeScript のソースコードを書いていて Webpackやtscコマンドでトランスパイルした時にデフォルトでは .map ファイルも自動的に作成されます。

このソースマップファイルをエディタ上のファイル一覧には表示したくないので、その設定方法のメモです。

{
    "files.exclude": {
        "**/*.js.map": true,
        "**/*.js": {"when": "$(basename).ts"},
        "**/.git": true,
        "**/.DS_Store": true
    }
}


"**/*.js": {"when": "$(basename).ts"}

と書くと、「同じ名前で拡張子が .ts のファイルが存在する場合だけ .js を非表示にする」という事が出来るとの事。

これは便利。



【VSCode】Visual Studio Code 0.5.0の新機能まとめ - Tumbling Dice http://outofmem.hatenablog.com/entry/2015/07/20/154030

[VSCode]ファイルツリーの表示制御|杏z 学習帳
https://anz-note.tumblr.com/post/126106378651/vscode%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%83%84%E3%83%AA%E3%83%BC%E3%81%AE%E8%A1%A8%E7%A4%BA%E5%88%B6%E5%BE%A1






2016年7月17日

Azure Blob StorageにJavaScriptから直接アップロードする

Azure Blob Storageにブラウザ(JavaScript)から直接アップロードする必要があったので、簡単なサンプルを作ってみました。

https://github.com/mikehibm/AzureDirectUploader


JavaScriptでHTML5のFile APIを使ってフォルダ内のファイル一覧を表示し、「Upload」ボタンを押すと選択されたファイルを一つずつAzure Storageにアップロードします。


ブラウザから直接アップロードするとは言っても、最初に一度だけサーバー側の処理を呼び出す必要があります。ASP.NET WebAPIはそのためだけに使っています。

これはAzure Storageアカウントのアクセスキーをクライアント側に渡さずに、一時的に「Shared Access Signature(共有アクセス署名)」というものを生成してそれをクライアント側で使える様にするためです。

一度SASを取得してしまえば、有効期限が切れるまでそれを使ってブラウザからAzure Storageにアップロード出来ます。サンプルでは有効期限が切れたら再度SASを取得し直してアップロードを継続する様になっています。

クライアント側から直接アップロード出来ればサーバー側の負荷が減って便利ですね。






.

2015年9月18日

React.jsの練習に◯☓ゲームを作ってみた。

ある日の深夜1時にふと思い立ってどうしても実際にReactで何か作りたくなり、朝5時までかかって大体の部分を作りました。

終了判定の部分がバグっていたりしたので2時間ほど眠ってからまた3時間ほどかけてデバッグ。翌日にまた約1時間かけて調整をして一応出来ました。

合計8時間もかかってますね。 汗)   
何せ初めてのReactなので。

クリックすると別ウィンドウでRunstant Liteが開きます。
https://goo.gl/Psx5fl

使ったツール


@phi_jp さん作成の「Runstant Lite」素晴らしいです。
ソースコードをエンコードしてURLに保存するというアイデアには脱帽しました。



初めてReact.jsで書いてみた感想


実は今AngularJSも勉強中ですが、それと比べるとReactの方が覚えないと行けない事が圧倒的に少ないので精神的に楽です。

まず親コンポーネントをとりあえず作って四角いDIVを表示する事が出来れば、そこに一つずつ子コンポーネントを付け足して行く感じでUIの作成はそれほど苦労せずに出来ました。

今回は

  • Board (盤面の背景を描画)
  • Cell  (個々のセルを描画)
  • StatusPanel (「X」の番です、などゲームの状態を表示)
  • ResultPanel (「X」の勝ち!、引き分け、などの結果を表示)

の4つのコンポーネントを作成しました。

最初は個々のCellにStateを持たせてクリック時の表示の更新をCellの内部で行っていました。でもそれだとBoard全体としての処理(終了判定など)との連携が面倒になる事に気付いて、Reactの世界の外側に作ったAppクラスで全ての状態を管理する様にしました。

ReactはあくまでView担当なので、Modelは自分で作らないとダメなんですよね。

そうなると今度は
1. Reactのコンポーネント内で発生するイベント(クリックされた、など)をModel側に伝える方法
2. 逆にModel側で発生するイベント(勝敗が着いた、など)をReactコンポーネント側に伝える方法
が必要になります。

前者の場合は単純にonClickなどのハンドラからAppクラスの関数を直接呼ぶ事も出来るのですが、後者のやり方が分からずにちょっと悩みました。

そこでFlux関係の解説を読み漁って、「とにかくPubSubで疎結合にしろ」という事なのかな〜と理解しました。

その結果、自分なりにEventEmitterもどき(?)を作って、Reactコンポーネントとモデルが必ずそれを経由して対話する様な形になりました。よくFluxで言われる「単方向のフロー」にはなっていない様な気もしますが、一応「PubSubで疎結合」にはなっているのではないかと思います。

こういうのはやっぱり自分で作って試行錯誤して見ないと腹の底から理解する事は出来ないですね。

単純な◯☓ゲームでしたがとりあえず作ってみて、Reactへの理解を少し深める事が出来たと思います。Android版のReact Nativeも出て来た事だし、これからもさらにReactに時間を投資して見ようかなと思います。


※追記 


きちんとFluxに則って作られたきれいな◯☓ゲームを見つけました。コードも見た目もきれいです。これは参考になります。

https://github.com/hackhat/tic-tac-toe-flux

ついでに、Reactで作られたゲームの一覧もありました。

36 Game Examples with ReactJS
http://react.rocks/tag/Game



React.jsの勉強のために読んだ本


入門 React ―コンポーネントベースのWebフロントエンド開発



参考になったサイト


reactjs - Reactデザインパターン - すべてがeになる - Qiita
http://qiita.com/shunjikonishi/items/ba6d981880e316131836

Flux – Dispatcher【日本語訳】と実装のポイント | mae's blog
http://mae.chab.in/archives/2738

一人React.js Advent Calendar 2014 - Qiita
http://qiita.com/advent-calendar/2014/reactjs

チュートリアル | React
https://facebook.github.io/react/docs/tutorial-ja-JP.html




.

2015年6月27日

JavaScriptでスマホゲームアプリを作る - Unity編

前回のCocos2d-JS編からだいぶ間が空いてしまいましたが、今回はUnity編です。

Unityのバージョンも5になって、新しいGUI機能が追加されていたりして2dゲームの作成もかなりやり易くなっている様です。

今回Unity5を使って出来たものはこんな感じです。



https://github.com/mikehibm/15puzzle-unity


● 新GUI(uGUI)の使い勝手


定番のNGUIを使った事が無いので比較できないのですが、uGUIは特に問題無く使えそうです。
気になるマルチ解像度への対応も簡単。どんな画面サイズでもボタンを常に右上に置きたい、という場合でもアンカーのプロパティ設定だけで行けるので気に入りました。今回のアプリではタイトル画面のメニュー、ゲーム画面のホームボタンとリスタートボタンをuGUIで作成しています。



● Tweenライブラリが色々あって迷う


パズルのピースを移動する時などにアニメーションをするためのTweenライブラリですが、いろいろと選択肢があるので結構迷います。

今回は LeanTween というのを使ってみました。他に iTween, Hotween なども良さそうでした。

LeanTweenは単体のアニメーションを実行するだけなら使い易いのですが、複数のアニメーションを組み合わせて順次実行し、それをさらにループさせたいという様な場合にコールバックを多段に繋がないと行けなくなるのでちょっと面倒です。


● UnityのJavaScriptは普通のJavaScriptではない


これは一番困った点かも知れません。UnityのJavaScriptは、C#にJavaScriptの皮を被せた様な妙な言語でした。。。

Unity の JavaScript でよくわからないことメモ

こんなヘンテコな言語に慣れるぐらいならいっそC#で開発した方が良いと思います。次にUnityで作る時は迷わずC#ですね。


● Cocos2d-JSと比較した感想


GameObjectとComponentの関係、GameObjectにスクリプトを貼り付けて行くやり方を分かった瞬間に、「Unityええやん!」と思ってしまいました。

Coco2d-JSと比べても、もしかするとUnityの方が生産性が高いかも。。。
今まで1年以上もCocos2d-x/JSで開発して来た私ですが、「もっと早くUnity始めてれば良かった」という気もちょっとして来ました。

次はUnity/C#でもうちょっと複雑なものを作って見ようかなと思います。






以上!

2015年1月13日

Cocos2d-JSでAdMob広告を表示する手順 (iOS, Android)

2015年1月12日現在の情報です。

開発環境


- Mac OS X 10.9.5
- Cocos2d-JS v3.2
- Cocos Code IDE v1.1.0
- Xcode v6.1.1
- Eclipse (ADT) 23.0.2


cocos newコマンドでプロジェクトを作成


cocos new -p com.example.CocosJSAdmob -l js CocosJSAdmob

(もちろんCocos Code IDEのメニューから新規プロジェクトを作成する事も出来るのですが、そうすると生成されるファイルが微妙に違ったりするので私はcocosコマンドを使う事にしています。)


Cocos Code IDEでプロジェクトを開く


現状ではcocos newコマンドで作成したプロジェクトをそのままCocos Code IDEにインポートしようとしても「プロジェクトが見つからない」というエラーになるので、まずプロジェクトのルートディレクトリに下の内容で .project ファイルを作成する必要があります。

<name>CocosJSAdmob</name>の部分だけ適宜変更して下さい。

.projectファイルを作成した後、Cocos Code IDEにインポートします。





Cocode Code IDE上で実行出来る事を確認しておきます。



ネイティブコードサポートを追加する。


Cocos Code IDEのCocos Toolsメニューから「Add Native Codes Support」を選択します。
これによってプロジェクトの下に frameworks/runtime-srcというディレクトリが出来ます。



Xcodeでプロジェクトを開く


下のディレクトリにXcode用のプロジェクトファイルがあるのでそれを開きます。

  /(プロジェクトルート)/frameworks/runtime-src/proj.ios_mac/



ターゲット(実機またはシミュレータ)を選んでそのまま実行するとビルドされて実行出来ます。ただ初回は全てのソースファイルをコンパイルするので数分かかるかも知れません。



iOSでのAdMob対応


下を見ながらAdMob SDKを組み込みます。

https://developers.google.com/mobile-ads-sdk/docs/admob/ios/quick-start

AdMob SDKのファイルをプロジェクトに追加。



ライブラリを追加。

下の青枠内が今回追加したもの。特に「GameController」はGoogleのドキュメントでは記載漏れになっているみたいなので要注意です。



-ObjCリンカフラグを追加。


*追記:
AdMobの最新版(v7以降)では-ObjCリンカフラグは不要になったそうです。


ソースコードを変更。


AppController.h
AppController.mm

dealloc関数の後ろ、@endの前に以下を追加。

JavascriptからObjective-Cのコードを呼ぶ。


詳細は下記を参照して下さい。
 How to call Objective-C functions using js on iOS/Mac

app.jsの適切な場所に下のコードを追加します。今回は HelloWorldLayer の ctor関数の一番最後に追加しました。

jsb.reflection.callStaticMethod("AppController", "ShowAdView");


iOSでAdMob広告が表示されたところ

広告を隠したい時はHideAdViewを呼べばOKです。

jsb.reflection.callStaticMethod("AppController", "HideAdView");

iOSでの対応は以上で終わりです。




Eclipse(ADT)でプロジェクトを開く


下のディレクトリにEclipse用のプロジェクトがあるのでそれをインポートします。

  (プロジェクトルート)/frameworks/runtime-src/proj.android/

インポートした後、プロジェクトのディレクトリで

cocos compile -p android

を実行して全てのソースコードをコンパイルしておきます。これも初回は数分ぐらいかかるかも知れません。


AndroidでのAdMob対応


次のドキュメントに従って Google Play Serviceを組み込んで下さい。

https://developers.google.com/mobile-ads-sdk/docs/?hl=ja


Java側にAdMob広告を表示する処理を追加。


AppActivity.javaに以下の変更を加えます。

1. AppActivityクラスの先頭に変数を追加。

private static Activity activity = null;
private static AdView adView = null;


2. onResumeでactivityを変数に保持。

@Override
protected void onResume() {
super.onResume();
activity = this;
};

3. AppActivityクラスに3つのメソッドを追加。

public static void createAdView()
public static void showAdView()
public static void hideAdView()

変更後のAppActivityクラスは下の状態になります。
(xxxxxxxxxxxxxxxx...xxxxxxの部分は適宜置き換えて下さい。)

JavascriptからJavaのコードを呼ぶ


app.jsを下の様に変更します。


Android上でAdMob広告が表示されたところ


Androidでの注意点


現状、Google Play Serviceへの参照をプロジェクトに追加して一旦Eclipse上でRunしてしまうと、それ以降は cocos run, cocos compile, cocos deployなどのコマンドが下の通りエラーで中断してしまいます。


-package-resources:
     [aapt] Creating full resource package...
     [aapt] invalid resource directory name: /Applications/android-sdk-mac_x86/extras/google/google_play_services/libproject/google-play-services_lib/bin/res crunch

BUILD FAILED
/Applications/android-sdk-mac_x86/tools/ant/build.xml:932: The following error occurred while executing this line:
/Applications/android-sdk-mac_x86/tools/ant/build.xml:950: null returned: 1

Total time: 43 seconds
Error running command, return code: 1


原因はADTとAntでビルドの方法(一時ファイルの場所など)が異なる事にあるみたいです。
( http://stackoverflow.com/questions/19746319/how-to-solve-invalid-resource-directory-name-resource-crunch )


対策としては、Eclipseを完全に終了するかまたはEclipseの自動ビルドをオフに設定してから cocosコマンドを実行すればエラーにならない様です。



以上、Cocos2d-JS v3.2でAdMob広告を表示する方法でした。

jsb.reflection.callStaticMethod という関数を使えばJavascriptからObjective-CやJavaのメソッドを簡単に呼び出せるので便利ですね!

これを使えば広告の表示制御だけでなく、
  • ブラウザで任意のURLを開く
  • AppStoreに飛ばす
  • Facebook, Twitterなどに投稿する
など色々な事が簡単に出来そうです。









 .

2014年12月30日

Azure, Node.jsとsocket.ioで作る早押しクイズ大会アプリ

先日の忘年会のネタに早押しクイズ大会アプリを作りました。




1.主催者がアプリのURLをブラウザで開き、クイズ大会を開始。
2.画面にパスコードが表示されるのでアプリのURLとパスコードを参加者に伝える。
3.各参加者はスマホのブラウザでアプリのURLを開いてパスコードを入力。
4.人数が集まったら主催者が「開始」ボタンを押す。
5.参加者の画面に問題が同時に表示される。
6.最初の正解者が3点、2番目の正解者が2点、3番目が1点獲得。不正解はマイナス1点。
7.5〜6を繰り返す。
8.全ての問題が終わったら結果発表画面を表示。

という流れで動きます。


ソースコードはこちら。
https://github.com/mikehibm/EnkaiQuiz

ホスティングはもちろんAzure Web Sites。

コーディングは全てVS Online上で行いました。Azure Web SitesとVS Onlineを組み合わせるとNode.jsアプリの開発はローカルに環境を一切作らなくても出来るのでとても便利です。

快適にコーディング出来たVS Onlineの画面

一応、同時に複数のクイズ大会をホスト出来る様になっています。

今回初めてsocket.ioを使ってサンプルのチャット以外のアプリを作りましたが、思っていたよりスムーズに作る事が出来ました。

とは言っても、サーバー側からクライアントにメッセージを送るのに io.emit() を多用してしまっているのはまずいかも。本来なら特定のクイズ大会に参加している人のみに送信するべきですね。

リファレンスを見ると、「Custom namespaces」を使う方法と「Rooms」を使う方法の2種類あるのかな(?)
http://socket.io/docs/rooms-and-namespaces/

実は他にも色々と改善が必要な点が。(笑)

- サーバー(Node)のプロセスが落ちたら全部消える。→ サーバー側のデータはDBに永続化しないと。
- クライアント側のデータも出来ればLocalStorageに格納したい。
- エラー処理をもっとちゃんとしないと。
- 単体テストは...  ...


見直せば見直すほどいろいろ出て来ますが、また余裕が出来たら改善しようかと思います。今回の忘年会では参加者10人で問題なく動いてくれたのでとりあえずいいかなと。



残念ながら今年の忘年会シーズンはほぼ終わってしまいましたが、新年会で使えるかも(?)

クイズの問題と解答のデータは public/js/data.js にJSON形式で入っているので変更するのも簡単です。

実行時に動的に外部からデータだけ読み込める様に改造するのも良いですね。

動かしてみたい方はぜひGitHubのソースをForkして試してみて下さい!







 
.