TRPGダイスツール再現してみた

　入社して10か月、高校生のころはよくクラスの人たちと
夜な夜なTRPGのセッションをしてたな…などと学生の頃を思い出して
少しじんわりしている今日この頃です。

会社でJSについて学んでいる中でランダムな数値を取得する、
Math.random() という関数が登場し、TRPGのダイスツールを再現できるのでは？
と思いましたので息抜きがてら作っていきたいと思います。
## TRPGとは？
各プレイヤーが自分で作ったキャラクターを演じ、
プレイヤー同士で会話しながら物語を勧めていく対話型のゲームです。
その過程でいろいろな種類のサイコロを振るダイスロールの機会があり、
その際に使用するツールとしてダイスツールと呼ばれるものがあります。
## Math.random()
Math.random() 関数は、 0 以上 1 未満の範囲で
小数点以下を含むランダムな数字を返します。
ダイスツールを作るうえで外すことのできない要素になります。
## 作成目標
ダイスツールを作成する上で欲しい要素として、
TRPGではCoC方式で主に遊んでいたので、
100面ダイスを振る際の達成値の設定、判定の成功失敗、
その他何面ダイスを何個振るかをカスタムできる機能、
ダイスロールのログを表示する機能が必要であると考えたので
HTMLとJavaScriptを使って作っていきたいと思います。

↓以下が完成したものになります。
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/a3b5f376-b43c-1436-d6e7-56e5da4e9cb3.png)


## ダイスツール作成
### 1D100ロールの作成
CoCTRPGを行う上で外すことのできない1D100ロールを作成していきます。
※1D100ロールとは100面ダイスを１つ振ることです。
**HTML**
それぞれ\<html>、\<head>、\<body>要素を作成し\<body>の中に
ダイスロールのサイトであること、1D100の判定を行う範囲であることの
見出しとするためにそれぞれ\<h1>、\<h2>、\<h3>要素で囲みます。
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/80ceaad9-46fb-265d-d5e4-d1d6d65b8ccf.png)

判定を行うかどうかの有無をチェックするために入力欄要素として
\<input>でtype="checkbox"を選択します。
判定という\<label>を作成し、ラベルとチェックボックスの紐づけ、
JSで処理を書くためにfor,name,idにそれぞれ"check"という名前を付けます。
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/445302e5-25cc-4ccb-f72c-24d7025dc45e.png)

達成値を入力するための入力要素として、\<input>でtype="number"を選択します。
ラベル「達成値」との紐づけ、JSで処理を書くための準備として
型や属性に"achieve"という名前を付けます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/2e1f84ea-74fa-0eec-ae80-83aec48ece9b.png)

ダイスロールを行うためのボタンを設置します。
idとして"rollBtn100"という名前を付けます。
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/c9ea406e-00c7-c803-c5ed-0fc930c1da4d.png)

結果を表示するための要素を用意します。
\<h2>要素で「結果」を囲み、結果を表示するための要素として
空白の\<h3>要素を用意し、idを"result"にします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/39cb17a2-040f-1644-5887-338a80183b07.png)

ダイスログを表示するための要素を用意します。
\<h2>要素で「ダイスログ」、ログを表示するために空白で
数字無しリストの要素として\<ul>、idを"log"として用意します。
また、ログをリセットするための要素としてボタンを用意し、
idを"reset"とします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/c9d13fb9-71fd-8f68-61ef-27f9b3bf66e4.png)

結果は以下となります↓
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/dab2a07b-5cb9-f71a-f227-12953a4fae41.png)

また、今からJavaScriptで処理を書いていくのでHTMLへJSファイルの読み込みを行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/db3cd4de-f450-38b0-2f7c-8bc99a28f5f4.png)

**JS**

最初にダイスロールボタンを押したら1D100のダイスロールを行い
結果とログを表示する処理を書いていきます。

htmlで用意した要素を使うためそれぞれ
「document.querySelector」でidを使い指定していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/18bbf164-8002-e206-0ece-c42a8b376313.png)

そして、今回のメインとなる乱数を作成する関数「rand」を作成します。
1～100の範囲でランダムに数値を返します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/6a6192f8-f05d-da67-f052-e947c6787358.png)

ダイスロールのボタンを押されたら結果を表示する
という処理を書きたいのでaddEventListenerで
第一引数に"click"を指定します。
第二引数で書く処理としてrand関数で生成された値を入れる変数「roll100」を定義。
結果の空白にしてあったh3要素「result」に.innerTextを使い結果を表示します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/0ce3b497-42c0-7af8-0efb-114990a193bf.png)
また、歴代の結果をログとして表示したいのでログを追加するための関数の作成をします。
変数newLogとしてdocument.createElementを使いリスト要素を作成します。
ダイスロールの結果を.appendを使用しリスト要素の中へ入れます。
.prependを使い、そのリストをこのhtmlのulの最初の子要素の前に挿入します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/ac30c473-cd12-e79a-243b-300fb1d22473.png)
ログ追加のための関数ができたので、先ほど作成したイベントの処理の中で
関数が処理されるよう書いていきます。関数logAddの引数として
ログに表示したい結果の内容を渡します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/f08852f8-bf41-07ce-64e4-0d55a7bac2bc.png)

ダイスロールボタンを押下するたびに結果が表示され歴代のログも残るようになりました。
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/64d0faec-e0f8-b630-0522-173441fa9e03.png)

続いてはログを削除するための処理を書いていきます。

リセットボタンが押されたらログのul要素の子要素であるli要素がなくなるまで
li要素を削除する処理にします。
イベントの発生条件としてaddEventListenerを使い、
ulの子要素があるかどうかはwhileを使い判断します。
ul要素はidが"log"で、document.querySelectorで"log"という名前で定義したので、
ul要素の子要素は「log.firstChild」と表現することができます。
子要素自体の削除は.removeChildを使用します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/bc70254e-b235-0e74-2317-90ab03ab77a4.png)
また、リセットボタンが押下されたら結果表示も表示されないようにしたいので、
.innerTextを使い結果に空欄を入れます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/f5f84953-053b-0daf-0110-15a00360fa97.png)

画面ではわからないですが…以上をもってリセットの処理を書くことができました。
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/85f42584-b776-2cab-ee87-2fd42ba71a9d.png)

続いてダイスロールの判定と達成値についての処理を書いていきます。

ダイスロールを判定について、判定する必要がないのに
達成値入力の欄があっても困るので、デフォルトで非表示にする処理を書きます。
.style.displayから"none"にすることで要素を消すことができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/4c0fa533-75d1-a0bd-1387-4f320bff119f.png)
チェックボックスの値を変更したときに処理を行いたいので、
addEventListenerから第一引数を"change"にします。
それぞれチェックボックスにチェックが入っているときは
判定を行うということで、達成値の入力欄を表示、
チェックボックスにチェックが入っていないときは
判定を行わないので、達成値入力欄を非表示にします。
判定にチェックが入っているかどうかはcheck.checkedから取得します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/5af793bf-2efc-da38-456b-337b2d38d9ef.png)
結果
↓チェックなし↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/d67e67b1-d270-b515-f915-40d846d996b0.png)
↓チェックあり↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/09038299-ebd1-9d62-2b9e-74d060191e9f.png)

判定の有無についての処理を書けたので達成値を入力した際の
判定の成功失敗についての処理を書いていきます。

判定を行うかどうかの条件分岐はcheck.checkedから判断し、
チェックが入っていなかった場合は判定しないので先ほど書いた1D100の処理を行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/30434ff2-2897-6fb7-e5e9-b555d61c63f6.png)

チェックが入っていた場合の処理を書いていきます。
そもそも1D100では1～100の範囲でランダムな数字を取得するので
達成値は1~100の範囲である必要があります。
達成値として入力された数字が0未満や100より大きい数字の範囲で
入力された場合はアラートメッセージを表示したいと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/e310610d-23d7-a610-36a6-b0b174747fcf.png)

表示されました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/0b7331a8-0089-b68a-e850-e465992f4c46.png)

それではチェックが入っていてかつ、達成値が適切な範囲であった場合の処理を書きます。
達成値に入力された値を取得し、その数値とダイスロールで生成された乱数を比較し、
達成値よりダイスロールの値が小さかった場合は成功。
大きかった場合は失敗として条件分岐させていきます。
達成値に入力された数値は.valueより取得することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/f65717c9-65a2-1384-46ce-f41baf2ff211.png)
このままでも判定の成功失敗は結果やログに表示されますが、
結果にそのまま表示されても面白みがないので、テキストに色を付けたいと思います。
.style.colorから色の変更が可能で成功は青色、失敗は赤色で表示したいと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/b6df351a-13c1-eef1-ad54-0a1e044d8016.png)
結果やログに反映されました。
↓成功↓
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/7636646a-eb75-e36d-75f1-9a012606b465.png)
↓失敗↓
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/7a105c49-ec66-c3e5-1f06-5d2e60446c40.png)

### カスタムロールの作成
以上の作業で1D100ロールの処理を書くことができました。
続いては自分でロールするサイコロの種類や数を設定できる
カスタムロールの作成に入りたいと思います。

**HTML**
カスタムロールの画面は1D100ロールと結果を表示する要素の間に用意したいと思います。
最初にカスタムロールの場所であることを示すために\<h3>で「カスタム」と表示します。
何面ダイスを何個振るかについて「〇D〇」の形で入力させたいので、
「D」の前後にnumberタイプでinputを用意します。
※〇D〇とは3D6の場合、6面ダイスを3回ロールするという意味になります。
それぞれにidをbeforDiceとafterDiceから"bfrD"と"aftD"とします。
カスタムロール用のボタンを用意、idは"rollBtnCus"とし、htmlは完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/46b5998c-2fac-2f8a-7304-fbdd8453c4aa.png)
↓実際の表示↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/089d2d72-ea23-74f2-c5e3-7f6bda0f4d71.png)
**JS**
htmlに追加で用意した要素を使うためそれぞれ
「document.querySelector」でidを使い指定していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/e770caed-3512-463f-fa68-b733b5dff70b.png)

カスタムロール用の乱数を生成する関数を用意します。
1D100ロールと違い乱数をどの範囲で生成するかは入力された値を使用します。
何面ダイスを使用するかは〇D〇ではDの後の〇が該当するので「aftD」に
入力された値である「aftD.value」を使用します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/09fd965c-cbd5-aae9-8a9d-885cfbc7ca92.png)
続いてカスタムロール用のボタンが押されたときのイベント処理を書いていきます。
イベントの発生条件としてはカスタムロール用のボタンがクリックされたら
処理させたいので「.addEventListener」を使い第一引数を"click"とします。
第二引数として最初に数値が入力されていないときのカスタムロールを防ぐために
if文を使いDの前後の入力欄が空欄であったらアラートメッセージを表示するようにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/a30be19b-c8e5-f2d3-ebcb-0b81b9882c77.png)
入力欄が空欄の時メッセージが表示されました
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/98a548b9-d84a-1fb1-4a5a-1825c6868f57.png)
入力欄に数値が入力されていた時の処理を書いていきます。
カスタムロールでは指定された面数のダイスを指定された回数ロールし、
その合計を求める必要があります。
そのため、合計値を求める変数「sum」を用意し、for文を使い合計を求めていきます。
sumはボタンがクリックされるたびに中身を0にするようにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/5de6b844-ccaa-4076-52bb-eea8e7e45d77.png)
for文は繰り返す回数は「〇D〇」のDの前の〇の数値が該当するので
初期化式でnを1とし、条件式ではnの値がbfrDの中身bfrD.value以下の際に繰り返し
増減式として、繰り返すたびにnの値をひとつづつ増やしていきます。
また、繰り返すたびにカスタムロール用に作成した関数を使用してsumに加算していけば
計算の処理は完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/1213d892-bc20-b2ff-9bb7-2bca048f3f11.png)
結果やログへの表示は1D100ロールの処理時に使用していたものを使いまわします。
それぞれの数値の表示はテンプレートリテラルを使用します。
結果表示は判定有の1D100ロールを行った後にカスタムロールや
判定無しの1D100ロールを行うと前の判定時の結果の色が継続されてしまうので
テキストの色を黒くなる用の処理を書き足します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/a5c2db53-69d4-a558-c180-d123737beb1d.png)
↓カスタムロールの処理全体↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/dded9b57-9c2d-e70c-7634-fc45cf33eb7a.png)
指定された数値ごとのロール処理ができました。
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632606/e2ecd658-1b5f-d4f7-a4ee-0eebe8846db5.png)

## さいごに
　今回はhtmlとJavaScriptを使用してダイスツールの再現をしました。
機能としてはまだ足りていないこともあり、処理はもっと簡潔に書けたのでは？
せっかくだしCSSで見た目も凝ってみようか…など思うところはまだまだありますが、
ひとまずこれで完成としたいと思います。
　ダイスツールを作っていく中でそれぞれの関数やイベントの発生条件などの復習を
行うことができました。
そして、何より自分が昔使っていたツールの再現は楽しく、
作成していく中で自身の成長を感じることができました。
これからもっと知識をつけ色々なものが作れるようになれればと思います。
## 参考
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Math/random