二進数コードで弾き語り

情報系大学生の徒然技術ブログ。

身内卓で構築したクトゥルフ神話TRPGインフラ

どうも、にー兄さんです。
新年あけましておめでとうございます。
2019年もどうぞ、本ブログをよろしくお願い致します!

ということで新年一発目の記事はクトゥルフ神話TRPGについて書きます。
技術記事...とは違うかもしれませんが、最後までお付き合いいただけると嬉しいです!

【目次】

TRPGってなんだ?

TRPGをご存じない方もいらっしゃると思うので簡単に紹介します。
TRPGとは、テーブルトークロールプレイングゲームの略語で、複数人で対話(ロール)によってプレイヤーキャラクターの行動を決定しながらゲームクリアを目指すテーブルゲームです。言葉では少しわかりずらいと思うので、実際にプレイしている動画(リプレイ動画)をご覧にあるといいかもしれません。まにむさんの動画がわかりやすくていいので、リンクを貼っておきます。
www.youtube.com

オンセって何かと面倒くさい

TRPGは複数人で集まるため必要があるため、チャットアプリを用いたオンライン・セッション(オンセ)でやることが多いです。
反対に、リアルで集まって顔を見ながらセッションをする方式をオフライン・セッション(オフセ)と言います。

オンセはプレイヤー(以下PL)が離れていてもできるので便利なのですが、機械を使用する点やPLの意思が直接届きづらいことから、
結構めんどくさいし、セッション時間もオフセよりも長くなる傾向があります。

そんなこともあり、TRPGをする際に、ゲーム鯖を用いてやることもしばしばあります。一番有名な鯖はどどんとふ公式鯖ですね。
チャット、ダイスロール、マップ表示、PCコマ表示をリアルタイムで複数人に反映させてくれます。非常に強力なTRPGツールの一つと言えるでしょう。

どどんとふ入ります?ーいや、面倒だしいいよ

ただ、うちの卓では最近どどんとふは使いませんね。
単純にめんどくさいからです。なんと怠惰な..。これほど強力なツールなのに...

使わない理由は明確で、どどんとふがなくても回せるセッションは多いからです。
実際いつもマップ表示したりPCを動かしたりしませんからね。軽めのシナリオだったらいりません。

そんな装備で大丈夫か?大丈夫だ、問題ない。

TRPGはゲーム機を使用しない、軽量なゲームです。
シナリオとキャラシとダイスさえあれば最低限回せます。
オンセで回すシナリオなんてせいぜい2、3時間、多くて4時間くらいが関の山です。

だから普段やるようなシナリオだとどどんとふのすべての機能はいりません

TRPGインフラ?

TRPGインフラという言葉が必ずしもあっているのかはわかりませんが、「インフラ:下部構造、仕組みを支える物」という意味で私はこう呼んでいます。
ようは、TRPGをするためにどんなツールを使っているのかです。

我が卓のインフラ

さて、私の卓のインフラを紹介したいと思います。
メンバーは8人、セッションで集まる時はだいたい2~4人くらいで回します。

チャットアプリ

チャットはDiscordを使用しています。
実は前まではSkypeを使っていました。SkypeからDiscordに乗り換えた理由は以下の通りです。

  • 軽量
  • Slackライクに部屋が作れる
  • botが簡単に作れる
  • 絵文字がカスタマイズできる
  • デザインが素敵
  • ゲーム特化

この中にはSkypeに実装してある機能が含まれていますが、いずれもDiscordのほうが性能は上だと判断しました。
一つ目にあげた「軽量」についてですが、前に使っていたSkypeが何か月か前に大型アップデートをしまして、デザイン・操作性ともに一新されてから意識しました。
スマホで開くときにも毎回長いローディング時間...イライラしますよねぇ...。その点軽量なチャットアプリであるDiscordは勝っていました。
部屋が作れるのも結構重宝しています。現在うちの卓で作られている部屋は

  • 一般カテゴリ内
    • #General
    • #Random
    • #ミュート推奨雑談部屋
    • #セッション募集部屋
    • #おすすめリプレイ紹介
    • #共有ドライブ変更報告
    • #ジュークボックス
  • セッションカテゴリ内
    • #各種セッション部屋
    • #キャラシ提出倉庫
    • #セッション部屋(ボイス)
  • イラストカテゴリ内
    • #イラスト倉庫
    • #絵チャ場
    • #イラスト依頼
  • 開発カテゴリ内
    • #Bot実験室

という感じです。部屋は作っておけばログが残るので便利ですし、ピン止めもできます。
discordapp.com

キャラシ

キャラシは自作のExcelテンプレートを作りました。まだ配布は考えてないので詳しくは書けませんが

能力値を入力すると自動でその他のステータスが算出され、
技能値を入力すれば初期値と合算された合計値を吐き出し
それらのデータをグラフを使うことで可視化でき、
さらにA4印刷でちょうどよい大きさに最適化されています。

Excelのいいところは、データの計算と可視化が簡単にできる事ですね。
夏休みに6時間かけて作りました()

録音

自作です。うちの卓には開発課なる部署が存在し、2名が配属されていますが。
片方のつよつよプログラマがステレオ音源の録音・出力に対応した録音ソフトを作りました。
とても軽量で重宝しています。ありがたや...
配布とか予定あるのかな・・・?

ダイスロール

自作Botです。
↑のDiscordのメリットでBotが簡単に作れるとありましたが、まさにダイスロールbotを作りました。

とはいえ、実際作らなくてもBotは各所に落ちています。そんな中私が作ったbotが目指したのは使いやすさです。

機能をダイスロールだけにしぼり、「\r 1d100<70」みたいなコマンドライクではなく
なんでもかんでも「(数値)(Dかd)(数値)」という書式であれば反応するようにしました。
理由は、チャット中に半角とか普通じゃ使わない文字を打つのがめんどくさいと思ったからです。
pythonの強い文字列処理を使えば普段チャットをするのと変わらない操作感でダイスロールができます。そこはこだわりました。

BGM

Botを入れました。Discordのbotで、rhythmというBotがあります。
シリアスだったらYoutubeから暗いBGMを見つけてくるだけでボイスチャンネルで再生できます。
すばらしいBotです。ぜひ導入してみてください。

tech.kunimaly.com

共有サービス

Googleドライブを利用して、キャラシやセッション録音音声、セッションログを更新します。
これにかんしては導入中なのでこれからの展望というべきかもしれません。
GoogleドライブからGoogleスプレッドシートを利用すれば、オンラインでリアルタイム複数人編集が可能です。
最新のドキュメントがどこでもDL不要で利用できるので、「キャラシがない!」「ログ見たいから送って欲しい」なんてことはなくなります。

絵文字

Discordでは絵文字も自作できます。身内ネタで盛り上がるのも良し、セッション募集チャンネルで参加表明するのも良し。
素晴らしい機能です。

最後に

最初はツールを導入すると結構戸惑うのですが、セッションを重ねるごとに慣れていき、今では普通に使いこなしています。
皆さんも、TRPGインフラを構築し、円滑快適なクトゥルフ・ライフを満喫してみてはいかがですか?
この記事が少しでも参考になれば幸いでございます。

最後までお付き合いいただきありがとうございました。
いあ!いあ!クトゥルフ

λ式と再帰を使って連分数を計算するRubyワンライナーでπを近似する

お久しぶりです。もう定番ネタと化しているπの近似値ですが(諦)、今回は第3弾となります。

πの近似値を連分数で求める

公式

今回参考にした公式が、Wikipediaに載ってます。
wikipedia:連分数#様々な数の連分数展開

引用させていただくと、



\displaystyle \frac{4}{\pi} = 1+
\displaystyle  \frac{1^2}{3+
\displaystyle  \frac{2^2}{5+
\displaystyle  \frac{3^2}{7+
\displaystyle  \frac{4^2}{9+
\displaystyle  \frac{5^2}{11+
\displaystyle  \frac{6^2}{\ddots
}
}
}
}
}
}

あ、ちなみに↑の連分数なのですが、はてな記法で書くにはコツがありまして、
私はこんな感じで書いています。

<div style="text-align: center;">
[tex:
\displaystyle \frac{4}{\pi} = 1+
\displaystyle \frac{1^2}{3+
\displaystyle \frac{2^2}{5+
\displaystyle \frac{3^2}{7+
\displaystyle \frac{4^2}{9+
\displaystyle \frac{5^2}{11+
\displaystyle \frac{6^2}{\ddots
}
}
}
}
}
}
]
</div>

余談でした。

さて、問題は連分数をどうやって実装するのかですね

再帰を使う

やはり最初に思い浮かんだのは再帰でした。
イメージとしては



\displaystyle a_n = n_1 + \frac{n_2}{a_{n-1}}

これがテンプレートになって、これをイテレートしていくと



\begin{align}

\displaystyle a_n &= n_1 + \frac{n_2}{a_{n-1}} \\ \\

\displaystyle &=
n_1 + \frac{n_2}{n_3 +
\displaystyle \frac{n_4}{a_{n-2}}} \\ \\

\displaystyle &=
n_1 + \frac{n_2}{n_3 +
\displaystyle \frac{n_4}{n_5 +
\displaystyle \frac{n_6}{a_{n-3}
}
}
}
\end{align}

という具合に求まっていきます。再帰的ですね。

さて問題の上のπの公式をRubyに落とし込むと、こんな感じに

def pi_continued_fraction(num1, num2, loopcount)
  return 1 if loopcount <= 0

  num1 + (num2**2 / pi_continued_fraction(num1 + 2, num2 + 1, loopcount - 1))
end

puts 4 / pi_continued_fraction(1.0, 1.0, 25)

三項演算子の呪い

私「n?このコードもう少し短くできるよね?」

可読性「えっ?(困惑)」

私「三項演算子をすこれよ」

def pi_continued_fraction(num1, num2, loopcount)
  loopcount <= 0 ? 1 : num1 + (num2**2 / pi_continued_fraction(num1 + 2, num2 + 1, loopcount - 1))
end

puts 4 / pi_continued_fraction(1.0, 1.0, 25)

λ式の呪い

私「関数定義はやっぱ一行だよね」

可読性「やめとk」

ラムダ騎士団「いいぞ(食い気味)」

私「ラムダ式すこ」

pi_continued_fraction = ->(num1, num2, loopcount) { loopcount <= 0 ? 1 : num1 + (num2**2 / pi_continued_fraction.call(num1 + 2, num2 + 1, loopcount - 1)) }
puts 4 / pi_continued_fraction.call(1.0, 1.0, 25)

ワンライナーの呪い

私「2行...」

可読性「日本では古来から、奇数は縁起が良いと言われてきたらしい」

私「あぁ、三重塔とか五稜郭とか三十一文字とかそうだね」

「「一行にするか」」

puts 4/(pi_continued_fraction = ->(num1, num2, loopcount) { loopcount <= 0 ? 1 : num1 + (num2**2 / pi_continued_fraction.call(num1 + 2, num2 + 1, loopcount - 1)) }).call(1.0,1.0,25)

できればπの近似値をはじき出す関数

pi()

の定義がしたいので、
最終的には

puts pi = -> { 4 / (pi_continued_fraction = ->(num1, num2, loopcount) { loopcount <= 0 ? 1 : num1 + (num2**2 / pi_continued_fraction.call(num1 + 2, num2 + 1, loopcount - 1)) }).call(1.0, 1.0, 25) }.call

となりました。
ターミナルには

> 3.141592653589793

と表示されました。だいたい25項目よりも前には小数点以下15桁は求まるみたいです。

最後に

最後まで読んでいただき、ありがとうございました。

誰か可読性ちゃんを擬人化してくれると嬉しいです。

レンダラー開発のための測光学覚書 補講 コサインの半球積分

補講

前回書いた記事に、


 
\displaystyle
\begin{align}
\int_{\Omega} \cos \theta d\omega &= \pi
\end{align}
という式が出てきましたので、この解説を補講という形で解説したいと思います。

まず、Vol.1の記事から以下のことがわかります。


 
\displaystyle
\begin{align}
d\omega = \sin \theta d \phi d \theta
\end{align}
drumath.hatenablog.com

このことから、


 
\displaystyle
\begin{align}
\int_{\Omega} \cos \theta d \omega &= \int^{\frac{\pi}{2}}_{0} \int^{2\pi}_{0} \cos \theta \sin \theta d\phi d\theta \\
                                                           &= \int^{\frac{\pi}{2}}_{0} 2\pi \cos \theta \sin \theta d\theta \\
                                                           &= 2\pi \int^{\frac{\pi}{2}}_{0} \sin \theta \left( \sin \theta \right)' d\theta \\
                                                           &= 2\pi \left[ \frac{1}{2} \sin^{2} \theta \right]^{\frac{\pi}{2}}_{0} \\
                                                           &= \frac{2\pi}{2} \left( 1^2-0^2 \right) \\
                                                           &= \pi
\end{align}

レンダラー開発のための測光学覚書Vol.3 ランバート反射BRDF

 レン覚(レンダラー開発のための測光学覚書)です!前回の投稿からだいぶ時間がたってしまったこともあり、自分でも記事を読み返したり、勉強しなおしたりしてました。
そして、とうとうレンダラーの開発をはじめたので、備忘録として学んだことをどんどん投稿しようと思います。今回は前回説明した「BRDF」について理解を深めていこうと思います。読みきりサイズなので、是非最後までお付き合いください。

目次

ランバート反射について

 さて、今回のテーマとなっている「ランバート反射」とは何かについて、最初に説明しようと思います。ランバート反射は拡散反射の一種で、いうなれば「理想的な拡散反射」です。どういう意味かというと、

光の入射角度にかかわらず、出射光は全ての方向に均一に反射されます。反射面の見た目の明るさ(輝度)はどの方向から見ても一定です。

http://www.systems-eng.co.jp/column/column03.html

らしいです。放射輝度の分布を図で示すとこんな感じです。
f:id:drumath:20180704210330p:plain

ランバート反射のBRDFを求める

 さて本題です。ここで導出に必要な物理量をしたの表のように定めます。

物理量 記号
放射束  \Phi
放射照度  E
放射輝度  L
反射率  \rho
立体角  \omega
BRDF  f_{r}
面積  A

シチュエーションによって、添え字に i(入射)や r(反射)をつけることがあります。

まず、反射率 \rhoを定義します。反射率は、


 \displaystyle
\rho = \frac{d\Phi_{r}}{d\Phi_{i}}
と定義されます。入射する光のエネルギーと反射する光のエネルギーの比ということで、イメージはしやすいですね。
さて、この反射率の定義に沿ってBRDFを導出したいのですが、そのためには放射束から放射輝度まで次元を落としていく必要があります。

入射放射束を変形する

まずは入射放射束を変形します。とはいえ放射束の定義に従って


 \displaystyle
\begin{align}
\Phi_{i} &= \int_{A}\int_{\Omega} \left( \omega' \cdot n \right)L_{i} \left( x, \omega' \right)d\omega'dA \\
            &= \int_{A}E(x)dA \\
d\Phi_{i} &= E(x)dA
\end{align}

反射放射束の変形

 次に反射放射束を変形していきます。同じように定義から攻めていくと


 \displaystyle
\begin{align}
\Phi_{r} &= \int_{A}\int_{\Omega} \left( \omega \cdot n \right)L_{i} \left( x, \omega \right)d\omega'dA
\end{align}
ここで、ランバート反射した放射束は立体角によらずに一定なので

 \displaystyle
\begin{align}
\Phi_{r} &= \int_{A}L_{r} \left( x, \omega \right)\int_{\Omega} \left( \omega \cdot n \right)d\omega dA \\
d\Phi_{r} &= L_{r} \left( x \right) dA \int_{\Omega} \left( \omega \cdot n \right)d\omega
\end{align}
また、レンダリング方程式を参考にして

 \displaystyle
\begin{align}
L_{r}(x) = \int_{\Omega} \int_{A} f_{r}(x, \omega, \omega') \left( \omega \cdot n \right) L_{i}(x)dAd\omega
\end{align}
ここでも f_{r} L(x)は立体角に依存しないので、

 \displaystyle
\begin{align}
L_{r}(x) &= f_{r,d}(x) \int_{\Omega} E_{i}(x) d\omega \\ &= f_{r,d}(x)E(x)
\end{align}
となるので、

 \displaystyle
\begin{align}
d\Phi_{r} = f_{r,d}E(x)dA\int_{\Omega}(\omega \cdot n)d\omega
\end{align}

反射率からBRDFを求める

 さて、ここまで来たので簡単なのですが


 \displaystyle
\begin{align}
\rho &= \frac{d\Phi_{r}}{d\Phi_{i}} \\ &= \frac{f_{r,d}E(x)dA\int_{\Omega}(\omega \cdot n)d\omega}{E(x)dA} \\
&= f_{r,d}\int_{\Omega}(\omega \cdot n)d\omega
\end{align}
ここで \omega, nは正規化されているので、

 \displaystyle
\begin{align}
\int_{\Omega}\cos \theta d\omega &= \int^{\frac{\pi}{2}}_{0}\int^{2\pi}_{0}cos \theta \sin \theta d\phi d \theta \\
                                         &= \pi
\end{align}
drumath.hatenablog.com

となるので、


 \displaystyle
\begin{align}
f_{r,d}(x) = \frac{\rho}{\pi}
\end{align}
が導けました。

おわりに

全休の日を利用して書きましたが、復習の時間が多くて結局夜になってしまいました...眠い。あれ、明日1限あったようなきがs
最後まで読んでいただき、ありがとうございました。

参考

qiita.com

7/5追記

 \int_{\Omega} \cos \theta d\omegaの途中式が間違っていたので訂正しました。

Machinの公式を使ってπを近似する

どうも、drumathです。前回の記事からだいぶ日が経ってしまいましたね。
というのも、大学生活が始まり、なかなか執筆に時間がさけなかったのが原因です。
今日は気分で少し書いてみようと思ったのですが、テーマがまたπの近似という結果になってしまいました…(許して)

Machinの公式でπを求める!

冒頭で、大学生活が始まったと述べましたが、約1か月前の解析学の授業をきっかけにこのテーマを書こうと思いまして、
実はその授業の先生がπの近似値の世界記録を持ってるらしく(詳しい話はよく知りませんが...)、そんな経緯からなのでしょうか、逆三角関数の話をしているときにMachinの公式を教えてくださいました。
そもそも、逆三角関数は(辺の比)→(角度)という写像なので、πが絡む公式はいくつかあるらしく、下記のようなものが授業では紹介されました。


 \displaystyle
\begin{align}

2 \tan^{-1} \frac{1}{2} - \tan^{-1} \frac{1}{7} = \frac{\pi}{4} \\

2 \tan^{-1} \frac{1}{3} + \tan^{-1} \frac{1}{7} = \frac{\pi}{4} \\

4 \tan^{-1} \frac{1}{5} - \tan^{-1} \frac{1}{239} = \frac{\pi}{4} \\

\end{align}

実は、一番最後の「239!?」ってなるやつがMachinの公式です。

Machinの公式を示してみる

今からこの公式を使うわけですが、本当にこうなるのかとりあえず示します。
これには、


 \displaystyle
\begin{align}
\tan(\tan^{-1}x)=x
\end{align}
という性質を利用します。

まず、Machinの公式の両辺の正接をとります。すると


 \displaystyle
\begin{align}

\tan (4 \tan^{-1} \frac{1}{5} - \tan^{-1} \frac{1}{239} ) &= \tan \frac{\pi}{4} \\ &=1

\end{align}
となりますね。よって、証明のゴールは左辺を変形していって1にすれば良いわけですね。やってみましょう!


 \displaystyle
\begin{align}

\tan (4 \tan^{-1} \frac{1}{5} - \tan^{-1} \frac{1}{239} ) について、\\
\tan^{-1} \frac{1}{5} = a,
\tan^{-1} \frac{1}{239} = b
とする。\\

\end{align}


 \displaystyle
\begin{align}

また、\\
\tan 2a = \frac{2\tan a}{1-\tan^{2} a}  &= \frac {2 \cdot \frac{1}{5} } { 1-(\frac {1} {5} )^{2} } \\ &= \frac{5}{12}\\ \\
\tan 4a = \frac{2\tan 2a}{1-\tan^{2} 2a} &= \frac{120}{119}  \\ \\

よって\\
\tan(4a-b) = \frac{\tan 4a - \tan b}{1+ \tan 4a \tan b} &= \frac{ \frac{120}{119} - \frac{1}{239} } { 1 + \frac{120}{119} \cdot \frac{1}{239} } \\ &= 1

\end{align}
とまぁ、最後にごりっと計算すると1になるわけです。すごいな。

πを計算するには

さて、証明ができたので、さっそく使っていきましょう
この公式を使ってどうやってπを求めるかというと、、、
積分だ!
待ってたぜぃって感じですが、どちらかというと積分は最後にちょこっとするだけで(というか積分ほぼやらないか…)、メインはマクローリン展開です。

まず、下の式を与えておきます。


 \displaystyle
\begin{align}

(\tan^{-1} x)^{'} = \frac{1}{1+x^{2}}

\end{align}
証明はめんどくさいのでやりません。ところで、彼(↑)はマクローリン展開すると

 \displaystyle
\begin{align}

\frac{1}{1+x^{2}} = 1-x^2+x^4-...

\end{align}
こうなりますよね。証明はしません。めんどくさいので。
これらがわかると下の式が導けますね。

 \displaystyle
\begin{align}

\tan^{-1} x &= \int (\tan^{-1} x)^{'} dx \\ &= \int \frac{1}{1+x^{2}} dx
\\ &= \int (1-x^2+x^4-...)dx
\\ &= x - \frac{x^3}{3} + \frac{x^5}{5} - ...
\\ &= \lim_{n \to \infty} \sum^{n}_{k=0} (-1)^{k} \cdot \frac{x^{2k+1}}{2k+1}

\end{align}

おぉ、アークタンジェントがシグマで表せました!
ここで、


 \displaystyle
\begin{align}

f(x) = \lim_{n \to \infty} \sum^{n}_{k=0} (-1)^{k} \cdot \frac{x^{2k+1}}{2k+1}

\end{align}
とします。すると、Machinの公式より

 \displaystyle
\begin{align}

4f \left( \frac{1}{5} \right) - f \left( \frac{1}{239} \right) = \frac{\pi}{4} \\ \\

\therefore \pi = 16f \left( \frac{1}{5} \right) - 4f \left( \frac{1}{239} \right)

\end{align}

プログラム書いてみた

さて、本記事も終盤です。実際にプログラミングで近似します。
その前に、プログラムのイメージがしやすいように(?)上でやったf(x)をちょっと改変したg(x,n)を以下のように定義します。


 \displaystyle
\begin{align}

g(x, n) = \sum^{n}_{k=0} (-1)^{k} \cdot \frac{x^{2k+1}}{2k+1}

\end{align}
あんまり見た目は変わりませんが、プログラムの構造がわかりやすいと思ったので…
gを使ってπを表すと、以下のようになります。

 \displaystyle
\begin{align}

\pi = \lim_{n \to \infty} 16g \left( \frac{1}{5}, n \right) - 4g \left( \frac{1}{239}, n \right)

\end{align}
となりますね。つまり、gという関数を実装して、二つ連結してループで回す、という感じです。

以下、私が書いたRubyのコードです。

def maclaurin(x, n)
  sum = 0.0
  0.upto(n) do |k|
    sum += (-1.0)**k * x**(2.0 * k + 1.0) / (2.0 * k + 1.0)
  end
  sum
end

def machin(n)
  4 * maclaurin(1.0 / 5.0, n) - maclaurin(1.0 / 239.0, n)
end

gets.chomp.to_i.times do |i|
  puts "#{i}: #{4 * machin(i)}"
end

ちなみに、rubocopからは「引数名が短い!」って怒られてますw

破壊力ぅ...

どうしてMachinの公式がπの近似に使われるか。
それはこの公式の破壊力がすさまじいからです(語彙力)
なんとサンプル数(ここでいうn)を10にすると、もうRubyではこれ以上の近似はできません!
収束がめっちゃ早い。

0: 3.18326359832636
1: 3.1405970293260603
2: 3.1416210293250346
3: 3.1415917721821773
4: 3.1415926824043994
5: 3.1415926526153086
6: 3.141592653623555
7: 3.1415926535886025
8: 3.141592653589836
9: 3.1415926535897922
10: 3.141592653589794
11: 3.141592653589794
12: 3.141592653589794
13: 3.141592653589794
14: 3.141592653589794
15: 3.141592653589794

終わりに

今回、英語の宿題を残して爆速で記事を書きましたが、久しぶりに楽しかったです。
またヒマになったら記事を上げていこうともうので、よかったらご覧になってください。
最後まで読んでいただき、ありがとうございました~。

もはや自前鯖はいらない!Node.jsで作ったシンプルなチャットを開発~デプロイまでをブラウザだけでやる

今回は最近はまっているNode.jsについて書こうと思います。 昔からリアルタイムチャットアプリを作るのは夢だったので、今回はそれをお題にしてみます。 なお、同じ記事をqiitaにも投稿しました。

どんなものを、どうやって作るか

今回はこんなものを作ります。 image.png ルーム機能や、アカウント機能などない、シンプルなチャットです。ソケットの練習のために作ったみたいなものです。実際にデプロイしたものをこちらに公開しています。

どうやって作るの?

今回の開発では、リアルタイム通信機能が不可欠です。そのため、Socket.ioというパッケージを使います。Socket.ioと言ってしまったのでもうお分かりかもしれませんが、バックエンドはNode.jsを使います。オールJSで開発だ!いぇい ということで、今回の開発プランはこうです。

  1. Cloud9でBlankなワークスペースを作る
  2. Node.jsで鯖を立てる
  3. Socket.ioでソケット通信のイベントを記述(ここまでバックエンド)
  4. クライアントサイドのhtmlを整える
  5. クライアントサイドjsでソケットのイベント処理
  6. Sassでスタイルを整える(ここまでフロントエンド)
  7. Git周辺を整え、Herokuへデプロイ

ゆえに、今回はHTML、jsの基本はわかっている前提で、主にnode.jsやSocket.io、Herokuへのデプロイについて書きたいと思います。

Cloud9とは

Cloud9とは、ネット上で開発が行えるIDEで、Linux環境が使えます。私はWin機を使っているので、Linuxを使いたい時によく重宝しています。PythonRubyもnodeもperlphpも全部入ってるんでWebサービス開発にはもってこいの環境です。入門時は特に環境構築で手間取るので、入門者や、ちょっといじってみたいという方にお勧めです。 過去にCloud9についての記事も書いたことがあるのでよろしければ読んでみてください

開発だ!

Node.jsでHello

さて、まずはCloud9でワークスペースを「Blank」で作ります。 すると、最初にREADMEが表示されると思いますが、別に消していただいていいでしょう。ではワークスペースserver.jsを追加しましょう。フォルダのアイコンを右クリックか、ターミナルで

touch server.js

と打ってください。 では、さっそくNode.jsで鯖を立てましょう。 server.jsに以下のようにコードを書きます。

let server = require("http").createServer((req, res)=>{
    res.writeHead(200, {'Content-Type': 'text/plane'})
    res.write('Hello')
    res.end()
})

server.listen(process.env.PORT, process.env.IP, ()=>{
    console.log("server")
})

ポート番号とホストはCloud9からprocess.env.PORTprocess.env.IPを使えと言われます。 そして上のほうにあるRunボタンを押すとnodeが走ってCloud9でWebページがホストされますので、見てみてください。Runボタンを押したときに教示されるターミナルの一番上に表示されているURLがそれです。「Hello」と表示されたでしょうか。

Expressの導入

それではフレームワークであるExpressとソケット通信のパッケージ、Socket.ioを導入しましょう。まず、Node.jsのパッケージを管理するnpmを初期化します。

npm init

質問されますので、その都度適当なものを入力します。するとpackage.jsonが作られます。 そしたら

npm install --save express socket.io

とコマンドを打ってください。この2つがインストされるはずです。 では、Expressを使ってHello,Worldしてみましょう。コードは以下の通りです。

let express = require("express")
let app = express()

app.get('/', (req, res)=>{
    res.send("Hello, World")
})

app.listen(process.env.PORT, ()=>{
    console.log("Server listening")
})

「Hello,World」と表示されたでしょうか。静的な文字ばかり表示していても面白くないので、今度はHTMLファイルを表示させましょう。まず、viewsフォルダにindex.htmlを作ります。ターミナルに

mkdir views
touch views/index.html

とコマンドします。 そしてserver.jsに戻って

let express = require("express")
let app = express()

app.get('/', (req, res)=>{
    res.sendFile(__dirname + '/views/index.html') // この行を変更
})

app.listen(process.env.PORT, ()=>{
    console.log("Server listening")
})

とします。そしてviews/index.htmlに

<!DOCTYPE html>
<html>
    <head>
        
    </head>
    <body>
        <p><em>Hello. World!</em></p>
    </body>
</html>

とします。ではRunしてみましょう。斜体のハローワールドが表示されましたでしょうか。  ここまで見てきてわかる通り、鯖立てるのめっちゃ簡単ですよね。合計20行にも満たないコードですが、HTML表示までできました。ここから肉付けしていきましょう。いよいよSocket.ioの導入です。

 Socket.ioの導入

この後どんどんクライアントサイドのjsも書いていくのですが、いったんソケットの話をしましょう。わかりやすくするために図を作りました。

例えば、クライアントAがソケット通信でイベントを発生させると、それが鯖に通知され、それを処理します。

鯖は処理した内容に従ってまた新たなイベント(今回は同じイベントを発生させた)を発生させます。

鯖からイベント通知を受け取ったクライアントB、Cはイベントを処理します。

このような流れでリアルタイム通信が成り立ちます。以上のことを踏まえてソケット通信をしてみましょう。コードを書く前に、

mkdir js
touch js/index.js

とコマンドを打ってindex.jsを作っておきましょう。 まずはサーバサイドから書いていきます。

let express = require('express')
let http = require('http')
let app = express()
let server = http.createServer(app)
let io = require('socket.io').listen(server);
  
app.use(express.static(__dirname))

server.listen(process.env.PORT);

app.get('/', (req, res)=> {
    res.sendFile(__dirname + '/views/index.html');
});

io.sockets.on('connection', (socket)=>{
    socket.on('eventA', (eventData)=>{
        io.sockets.emit('eventB', {msg: eventData.message})
    })
})

上のrequire系はもう呪文だと思って書いています。ここで

app.use(express.static(__dirname))

ですが、これを行うことでjsファイルやcssファイルを読み込むことができます。最初読み込んでるはずのjsファイルが見つからないと言われ私はSANチェックだったのですが、ドキュメントを見てみると、ミドルウェアという仕組みを使って静的ファイルを読み込むことで解決するとのことでした。 そしてsocketの部分である

io.sockets.on('connection', (socket)=>{
    socket.on('eventA', (eventData)=>{
        io.sockets.emit('eventB', {msg: eventData.message})
    })
})

この部分ですが、簡単に言うと、eventAというイベントが発生したら、eventBというイベントを発生させよ、と言っているだけです。でもってeventAからはmessageというインデックスを持ったハッシュが渡されているわけです。このハッシュをeventDataという引数に格納して即時関数を実行し、新たにmsgというインデックスを持ったハッシュを渡しています。 クライアントサイドも見てみましょう。HTMLのほうはjqueryとsocket.ioと先ほど作ったjs/index.jsを読み込み、ボタンにonclickイベントを付け加えただけです。

<!DOCTYPE html>
<html>
    <head>
        
    </head>
    <body>
        <p><em>Hello. World!</em></p>
        <input type="text" name="msg-input" id="msg-input"/>
        <button id="msg-btn" onclick="sendEvent()">Send</button>
        <div id="msg-whole"></div>
        
        <!--追加-->
        <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
        <script src="/socket.io/socket.io.js"></script>
        <script type="text/javascript" src="js/index.js"></script>
    </body>
</html>
var socketio = io()

var sendEvent = function(){
    msg = $('input#msg-input').val()
    socketio.emit('eventA', {message: msg})
}

socketio.on('eventB', function(data){
    $('div#msg-whole').prepend("<div>"+ data.msg +"</div>")
})

さて、クライアントjsですが、至極シンプルなコードですね。 ボタンを押したらsendEvent()関数が呼び出されるのですが、そこではinputボックスの中身をmessageというインデックスのハッシュに入れて渡しています。先ほどやりましたね。そしてeventAが発生すると繋がっているすべてのクライアントPCにeventBが発生するので、そのイベントハンドラを記述しています。

socketio.on('eventB', function(data){
    $('div#msg-whole').prepend("<div>"+ data.msg +"</div>")
})

この部分ですね。単純にmsg-wholeのidを持つdivにちっちゃいdivをどんどん詰め込んでるだけです。 このようなコードから、 1. メッセージを送信 2. 鯖が受け取り、受け取ったメッセージをすべてのクライアントに送信 3. クライアントが受け取り、divの中に追加 という構図が浮かびますでしょうか。実際にタブを複製してやってみてください。リアルタイムで通信しているのがよくわかります。 image.png

私はこんな感じで作りました。

さぁ、ソケットは理解できたでしょうか?そもそも私が初心者なので拙い説明になってしまったと思うので、わからないところがあったら別途調べていただければと思います。しかし、ここまで理解できれば、あとはフロントを整えたり、イベントを増やしたりするだけでシンプルなチャットはできます。ぜひやってみてください。その一例として、私が実際に書いたコードを載せて、少し補足をさせていただきます。

.
├── js/
│  ├── index.js
│  └── def events.js
├── views/
│  └── index.html
├── style/
│  ├── index.sass
│  ├── _vars.sass
│  ├── _baseDesign.sass
│  ├── _msgStyle.sass
│  └── index.css
├── node_modules/
├── server.js
├── package.json
└── README.md

package.json

{
  "name": "express-socket-test-app",
  "version": "0.1.0",
  "description": "",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  "author": "drumath2237",
  "license": "ISC",
  "devDependencies": {
    "express": "^4.16.2",
    "socket.io": "^2.0.4"
  },
  "dependencies": {
    "express": "^4.16.3",
    "socket.io": "^2.0.4"
  }
}

server.js

これはあまり変わりませんね。変更点としては、メッセージを送ったユーザーの名前がわかるようにしたことです。こうすることによって、自分のメッセージと他人のメッセージによってスタイルを変えることができますので。

let express = require('express')
let http = require('http')
let app = express()
let server = http.createServer(app)
let io = require('socket.io').listen(server);
  
app.use(express.static(__dirname))

server.listen(process.env.PORT);

app.get('/', (req, res)=> {
    res.sendFile(__dirname + '/views/index.html');
});

io.sockets.on('connection', (socket)=>{
    socket.on('message', (data)=>{
        io.sockets.emit("message",
        {
            msg: data.message,
            name: data.name
        })
    })
})

views/index.html

これもあまり変えていませんね。

<!DOCTYPE html>
<html>
    <head>
        <link rel="stylesheet" href="style/index.css" type="text/css" />
        <link href="https://fonts.googleapis.com/css?family=Itim" rel="stylesheet">    </head>
    <body>
        <h1>Hello,<input type="text" id="user-name-input" placeholder="put your name">! Send Messages!</h1>
        <input type="text" name="msg-input" id="msg-input"/>
        <button id="msg-btn" onclick="sendMessage()"> Send </button>
        <div id="msg-whole"></div>
        
        <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
        <script src="/socket.io/socket.io.js"></script>
        <script type="text/javascript" src="js/index.js"></script>
        <script type="text/javascript" src="js/def events.js"></script>

    </body>
</html>

js/index.js

makeMessage関数が少しややこしくなっていますが、スタイルを整えるためにやむなく・・・。

var socket = io()

socket.on('message', function(data){
    addMessage(data.msg, data.name)
})

var sendMessage = function(){
    var msg = $('input#msg-input').val()
    var name = $('input#user-name-input').val()
    socket.emit('message',
    {
        message: msg,
        name: name
    })
    $('input#msg-input').val('')
}

var addMessage = function(msg, username){
    var msgWhole = $('div#msg-whole')
    msgWhole.prepend(makeMessage(msg, username))
}

var makeMessage = function(msg, username){
    if(username===$('input#user-name-input').val())
        return "<div style='text-align: right'><div class='my-msg'><span class='user-name'>" + username + ":</span><span class='message'>" + msg + "</span></div></div></br>"
    else
        return "<div style='text-align: left'><div class='other-msg'><span class='user-name'>" + username + ":</span><span class='message'>" + msg + "</span></div></div></br>"
}

js/def events.js

ボタンを押したときのアニメーションやエンターキーでMessageを送信するようなイベントハンドラを記述しています。jQueryしか使ってない。

$(function(){
    $('button#msg-btn').on('mousedown', function(){
        var button = $(this)
        button.css({
            'top': '3px',
            'border-bottom': '0'
        })
    })
    $('button#msg-btn').on('mouseup', function(){
        var button = $(this)
        button.css({
            'top': '0',
            'border-bottom': '3px solid white'
        })
    })
    
    $('input#msg-input').keypress(function(e){
        if(e.which == 13){
            sendMessage()
        }
    })
})

style/index.sass

さてSassでスタイルを整えていますが、htmlに反映するためにはコンパイルしてcssファイルにする必要があります。毎度毎度やっているのはめんどくさいので私の場合はターミナルをもう一個起動して

sass index.sass:index.css --style expanded --watch

としておけばSassファイルを監視してくれるので保存するたびに自動でコンパイルしてくれます。

@import "baseDesign"
@import "vars"
@import "msgStyle"

html, body
    margin: 0
    padding: 0
    background: white
    text-align: center
    background: $bg-color
    color: white
    font-family: 'Itim', cursive
    height: 100%
    input
        @include base-design
        border: 0px
        border-bottom: 2px solid white
        &#user-name-input
            width: 200px
            text-align: center
    button#msg-btn
        @include base-design
        border-radius: 3px
        height: 40px
        box-shadow: 0px 3px 0px 0px white
        position: relative
        top: 0
    div#msg-whole
        height: 50%
        width: 80%
        border: 2px white dashed
        margin: 0 auto
        margin-top: 20px
        overflow: auto
        padding: 10px
        div.my-msg
            @include msg-style
            background: #96ed9e
            color: white
            &:after
                content: ""
                position: absolute
                top: 20px
                right: -15px
                height: 0
                width: 0
                border-top: 10px transparent solid
                border-right: 0px
                border-bottom: 2px transparent solid
                border-left: 15px white solid
            span.user-name
                font-weight: bolder
                font-size: 18px
                margin-right: 10px
            span.message
        div.other-msg
            @include msg-style
            background: $bg-color
            color: white
            &:after
                content: ""
                position: absolute
                top: 20px
                left: -15px
                height: 0
                width: 0
                border-top: 10px transparent solid
                border-right: 15px white solid
                border-bottom: 2px transparent solid
                border-left: 0
            span.user-name
                font-weight: bolder
                font-size: 18px
                margin-right: 10px
            span.message

style/_vars.sass

変数などを定義するパーシャルですが、背景色しか定義しなかったんで全然varsみたいに複数形にすることなかった。

$bg-color: #96d7ed

style/_msgStyle

メッセージのスタイルを記述したパーシャルです。

@mixin msg-style
    border: 2px white solid
    line-height: 40px
    border-radius: 15px
    min-width: 100px
    max-width: 50%
    display: inline-block
    margin: 10px
    padding-left: 10px
    padding-right: 10px
    font-size: 20px
    position: relative
    word-wrap: break-word

style/_baseDesign.sass

コントロールのスタイルを規定したパーシャルです。

@mixin base-design
    border: white 2px solid
    line-height: 30px
    background: rgba(0,0,0,0)
    font-size: 30px
    color: white
    font-family: 'Itim', cursive
    padding: 5px
    margin: 5px
    &:focus
        outline: 0

herokuにデプロイだ!

Gitを整える

まずgit周辺をいじりましょう。コミットするだけなんですけどね。

git init
git add .
git commit -m "first commit"

とすれば、大丈夫です。

herokuにあげる

いよいよherokuにあげちゃいましょう。まずherokuのアカウントを作ってください。無料でクレジットカードなしだと5つまでしか上げられないみたいで、さっき怒られました。 herokuのアカウントを作成出来たらターミナルでherokuにログインします。

heroku login

メアドとパスを入力します。前はパス入力中は何も表示されなくて怖かったのですが、今はアスタリスクが表示されて安心します。ログインで来たら続けて以下のように打ちます。

heroku keys:add
heroku create
git push heroku master
heroku rename "<好きな名前(既に使われているのは使用不可)>"

pushは時間かかるので少し待ちます。 さてではherokuのマイページへ移動します。renameでつけた名前をクリック→右上の「Open App」をクリックで表示できたでしょうか。 お疲れ様です!無事デプロイできました!

おわりに

お疲れさまでした。初心者なりに記事を書いてみましたが、いかがだったでしょうか。 私はサーバサイドに全然詳しくなくて、もちろん鯖も持っていなければNode.jsやRailsもthe素人ですので、この機会にちゃんと勉強したいなぁと思いました。それにしてもheroku便利ですよね。まさにブラウザだけで完結するWebアプリ開発。Cloud9もすごい便利なので(ソーシャルコーディングとかできるし)是非、この機会に使ってみてはいかがでしょうか。 以上となります。最後まで読んでいただき、ありがとうございました! Happy Coding!

【小ネタ】区分求積法でπの近似値を求めるアルゴリズム

 どうも、drumathです。今回はちょっとしたチップスとして、πの近似値を求めたいと思います。使用する言語はC++です。

数Ⅲ生の十八番

数Ⅲで積分をしていると、置換積分やらなんやらのおかげで一見関係なさそうなところで \piが出てきます。
今回はそんな積分の中から、受験生の十八番ともいえるやつを使います。


 \displaystyle
\begin{align}
\int_{0}^{1} \frac{1}{x^{2}+1} dx
\end{align}
について、 x=\tan \thetaと置くと

 \displaystyle
\begin{align}
x \to 0のとき、\theta \to 0、\\\
x \to 1のとき、\theta \to \frac{\pi}{4}
\end{align}
また、

 \displaystyle
\begin{align}
dx = \frac{1}{\cos^2 \theta}d\theta
\end{align}
となるので、

 \displaystyle
\begin{align}
\int_{0}^{1} \frac {1} {x^{2}+1} dx &=\int_{0}^{\frac {\pi} {4} } \frac{1}{\tan^{2} \theta + 1} \cdot \frac{1}{ \cos^{2} \theta} d\theta\\\
&= \int_{0}^{\frac {\pi} {4} } \frac{\cos^2 \theta}{\cos^2 \theta}d\theta\\\
&= \int_{0}^{ \frac {\pi} {4} } d \theta \\\
&= [ \theta ]_{0}^{\frac{\pi}{4}}\\\
&= \frac{\pi}{4}
\end{align}
となります。つまり、

 \displaystyle
\begin{align}
\pi = 4\int_{0}^{1} \frac{1}{x^2+1}dx
\end{align}
ということがわかりました。

区分求積法で近似する

ここで、区分求積法を用いると、


 \displaystyle
\begin{align}
4\int_{0}^{1} \frac{1}{x^2+1}dx=4\lim_{n \to \infty} \frac{1}{n} \sum_{k=1}^{n} \frac{1}{(\frac{k}{n})^2+1}
\end{align}
となることから、 nをだんだん大きくしていけば、 \piの近似値が求まるみたいです。

コードを書く

では先ほどの結果をもとにコードを書いてみました。

#include <stdio.h>
#include <math.h>

double pi_calc(double);

int main(void)
{
  for (int i = 1; i < 1000000; i++)
    printf("\r%d: %f", i, pi_calc((double)i));
  return 0;
}
// 区分求積法を用いたアルゴリズム
double pi_calc(double n)
{
  double res = 0;
  for (int k = 1; k <= n; k++)
    res += 1.0 / (1.0 + pow(((double)k / n), 2.0));
  return 4.0 * res / n;
}

 nを100万まで動かします。
コードでところどころdoubleを使っているのは、floatを使ってしまうと誤差補正のせいで値がぶれてしまうことを防ぐためです。

今回は以上になります。最後まで読んでいただき、ありがとうございました。