Node.jsでWebアプリを作りCloudFormationで自動化する

はじめに

Node.jsはサーバサイドで動作するJavaScriptですが、ちょっとしたWebアプリを作るのに便利です。今回は麻雀点数計算アプリを作りたいと思います。CloudFormationはAWSのサービスの一つで、いつもGUIで行っているAWSのリソースプロビジョニングを自動化出来る機能です。これを使ってサーバを立て、Gitクローン、アプリのデプロイまでを自動化する事が目標です。

※ネイティブアプリも作成してみました。無料ですのでインストールしてみて下さい。
リンク

アプリ制作

GitHubで公開するのが手っ取り早いのですが、精神衛生上ユーザ名を公開したくないのでソースを解説しながら貼っていきたいと思います。お手数ですがコピペでお願いします。

ファイル構成

まずファイル構成と概要は次の通りです。

$ tree
.
|-- Dockerfile
|-- index.js
`-- templates
    |-- calc.ejs
    |-- form.ejs
    |-- index.ejs
    |-- mentsu.ejs
    |-- result.ejs
    `-- yaku.ejs
  • Dockerfile
    • Node.js, フレームワーク, テンプレートエンジン等環境構築
  • index.js
    • サーバ実行プログラム
  • templates/index.ejs
    • トップページ
  • templates/form.ejs
    • 入力フォームページ
  • templates/result.ejs
    • 計算結果出力ページ
  • templates/calc.ejs
    • 計算処理部分

Dockerfile

言語はNode.jsでフレームワークはExpress、テンプレートエンジンはejsです。

FROM centos:7

# Update & Setting Locale
RUN yum -y update
RUN yum reinstall -y glibc-common && yum clean all
RUN localedef -f UTF-8 -i ja_JP ja_JP.UTF-8
ENV LANG ja_JP.UTF-8
ENV LANGUAGE ja_JP:ja
ENV LC_ALL ja_JP.UTF-8
RUN unlink /etc/localtime
RUN ln -s /usr/share/zoneinfo/Japan /etc/localtime

# Install Node.js
WORKDIR /myapp
RUN curl -sL https://rpm.nodesource.com/setup_13.x | bash
RUN yum -y install nodejs

# Setting FrameWork
RUN npm init -y
RUN npm install express --save
RUN npm install ejs --save
RUN npm install body-parser --save

COPY ./index.js /myapp
COPY ./templates/ /myapp/templates/
CMD ["/sbin/init"]

/sbin/initsystemctlコマンドを使いたいから引数にしています。

index.js

サーバプログラムです。

var express = require('express');
var path = require('path');
var bodyParser = require('body-parser');

var app = express();

app.use(bodyParser.urlencoded({ extended: false }));
app.set('views', path.join(__dirname, 'templates'));
app.set('view engine', 'ejs');

/********************************/
/*          index.ejs           */
/********************************/
app.get('/', function (req, res) {
        return res.render('index');
});

/********************************/
/*           form.ejs           */
/********************************/
app.get('/form', function (req, res) {
	return res.render("form");
});

app.post('/form', function (req, res) {
	return res.render("result", {data: req.body});
});

app.listen(3000, function () {
});
  • 3行目:
    • body-parserはPOSTデータのパースに使用
  • 14~16行目:
    • http://【ホスト名 or IPアドレス】:3000でGETリクエスト時にindex.ejsを返す
  • 21~23行目:
    • http://【ホスト名 or IPアドレス】:3000/formでGETリクエスト時にform.ejsを返す
  • 25~27行目:
    • http://【ホスト名 or IPアドレス】:3000/formでPOSTリクエスト時にform.ejsを返す
    • 引数にオブジェクト名dataでリクエストボディデータを渡す

このようにURIで処理を振り分ける概念のことをRESTfulというらしいです。

各種テンプレートファイル

index.ejs

<a href="form">計算ページ</a>

form.ejs

<html>
<head>
<style type="text/css">
.box {
	display: flex;
	justify-content: space-between;
}
</style>
</head>
<body>
<h1>計算ページ</h1>
<form method="post">
	<div>
		<h3>役</h3>
		<%- include('./yaku'); %>
	</div>

	<div>
		<h3>上がり</h3>
		<p>
			<input name="agari" type="radio" value="lon" checked="checked" />ロン
			<input name="agari" type="radio" value="tsumo" />ツモ
		</p>

		<h3>親/子</h3>
		<p>
			<input name="oyako" type="radio" value="oya" checked="checked" />親
			<input name="oyako" type="radio" value="ko" />子
		</p>

		<h3>鳴き</h3>
		<p>
			<input name="naki" type="radio" value="ari" checked="checked" />有り
			<input name="naki" type="radio" value="nashi" />無し
		</p>

		<%- include('./mentsu'); %>
		<h3>面子(中張牌)</h3>
		<span>明刻</span>
		<input name="chMinko" type="button" value="+" id="chMinkoUp" />
		<input name="chMinko" type="button" value="リセット" id="chMinkoDown" />
		<input name="chMinko" type="text" value="0" id="chMinkoDisp" />
		<br>
		<span>暗刻</span>
		<input name="chAnko" type="button" value="+" id="chAnkoUp" />
		<input name="chAnko" type="button" value="リセット" id="chAnkoDown" />
		<input name="chAnko" type="text" value="0" id="chAnkoDisp" />
		<br>
		<span>明槓</span>
		<input name="chMinkan" type="button" value="+" id="chMinkanUp" />
		<input name="chMinkan" type="button" value="リセット" id="chMinkanDown" />
		<input name="chMinkan" type="text" value="0" id="chMinkanDisp" />
		<br>
		<span>暗槓</span>
		<input name="chAnkan" type="button" value="+" id="chAnkanUp" />
		<input name="chAnkan" type="button" value="リセット" id="chAnkanDown" />
		<input name="chAnkan" type="text" value="0" id="chAnkanDisp" />

		<h3>面子(幺九牌)</h3>
		<span>明刻</span>
		<input name="yoMinko" type="button" value="+" id="yoMinkoUp" />
		<input name="yoMinko" type="button" value="リセット" id="yoMinkoDown" />
		<input name="yoMinko" type="text" value="0" id="yoMinkoDisp" />
		<br>
		<span>暗刻</span>
		<input name="yoAnko" type="button" value="+" id="yoAnkoUp" />
		<input name="yoAnko" type="button" value="リセット" id="yoAnkoDown" />
		<input name="yoAnko" type="text" value="0" id="yoAnkoDisp" />
		<br>
		<span>明槓</span>
		<input name="yoMinkan" type="button" value="+" id="yoMinkanUp" />
		<input name="yoMinkan" type="button" value="リセット" id="yoMinkanDown" />
		<input name="yoMinkan" type="text" value="0" id="yoMinkanDisp" />
		<br>
		<span>暗槓</span>
		<input name="yoAnkan" type="button" value="+" id="yoAnkanUp" />
		<input name="yoAnkan" type="button" value="リセット" id="yoAnkanDown" />
		<input name="yoAnkan" type="text" value="0" id="yoAnkanDisp" />

		<h3>ドラ</h3>
		<span>ドラ数</span>
		<input name="dora" type="button" value="+" id="doraUp" />
		<input name="dora" type="button" value="リセット" id="doraDown" />
		<input name="dora" type="text" value="0" id="doraDisp" />

		<h3>雀頭</h3>
		<p>
			<input name="janto" type="radio" value="jibasanngen" checked="checked" />自風/場風/三元
			<input name="janto" type="radio" value="renpu" />連風
			<input name="janto" type="radio" value="sonota" />数/客風
		</p>

		<h3>待ち</h3>
		<p>
			<input name="machi" type="radio" value="penkantanno" checked="checked" />辺張/嵌張/単騎/ノベタン
			<input name="machi" type="radio" value="sonota" />両面/シャボ
		</p>
	</div>

	<div>
		<input type="submit" value="CALC" />
	</div>
</form>
</body>
</html>
  • 15行目:
    • yaku.ejs(役リスト)を呼び出す。
  • 37行目:
    • mentsu.ejs(面子ボタン関数)を呼び出す

yaku.ejs

<div class="box">
	<div class="item">
		<input name="1han" type="checkbox" value="立直" checked="checked" />立直<br>
		<input name="1han" type="checkbox" value="一発" />一発<br>
		<input name="1han" type="checkbox" value="門前清自摸和" />門前清自摸和<br>
		<input name="1han" type="checkbox" value="役牌" />役牌<br>
		<input name="1han" type="checkbox" value="タンヤオ" />タンヤオ<br>
		<input name="1han" type="checkbox" value="平和" />平和<br>
		<input name="1han" type="checkbox" value="一盃口" />一盃口<br>
		<input name="1han" type="checkbox" value="海底撈月" />海底撈月<br>
		<input name="1han" type="checkbox" value="河底撈魚" />河底撈魚<br>
		<input name="1han" type="checkbox" value="嶺上開花" />嶺上開花<br>
		<input name="1han" type="checkbox" value="槍槓" />槍槓<br>
	</div>
	<div class="item">
		<input name="2han" type="checkbox" value="ダブル立直" />ダブル立直<br>
		<input name="2han" type="checkbox" value="三色同順" />三色同順<br>
		<input name="2han" type="checkbox" value="三色同刻" />三色同刻<br>
		<input name="2han" type="checkbox" value="三暗刻" />三暗刻<br>
		<input name="2han" type="checkbox" value="一気通貫" />一気通貫<br>
		<input name="2han" type="checkbox" value="七対子" />七対子<br>
		<input name="2han" type="checkbox" value="対々和" />対々和<br>
		<input name="2han" type="checkbox" value="混全帯幺九" />混全帯幺九<br>
		<input name="2han" type="checkbox" value="三槓子" />三槓子<br>
	</div>
	<div class="item">
		<input name="3han" type="checkbox" value="二盃口" />二盃口<br>
		<input name="3han" type="checkbox" value="純全帯公九" />純全帯公九<br>
		<input name="3han" type="checkbox" value="混一色" />混一色<br>
	</div>
	<div class="item">
		<input name="4han" type="checkbox" value="小三元" />小三元<br>
		<input name="4han" type="checkbox" value="混老頭" />混老頭<br>
	</div>
	<div class="item">
		<input name="6han" type="checkbox" value="清一色" />清一色
	</div>
</div>

mentsu.ejs

面子選択UI部分。

<script>
window.onload = function() {
     // オブジェクトと変数の準備
     var count_disp = document.getElementById("chMinkoDisp");
     var count_up_btn = document.getElementById("chMinkoUp");
     var reset_btn = document.getElementById("chMinkoDown");
     var count_value = 0;
     // カウントアップボタンクリック処理
     count_up_btn.onclick = function (){
          count_value += 1;
          count_disp.value = count_value;
     };
     // カウントアップボタンのマウスダウン処理
     count_up_btn.onmousedown = function() {
          count_up_btn.style.backgroundColor = "#00FF00";
     }
     // カウントアップボタンのマウスアップ処理
     count_up_btn.onmouseup = function() {
          count_up_btn.style.backgroundColor = "";
     }
     // リセットボタンのクリック処理
     reset_btn.onclick = function (){
          count_value = 0; count_disp.value = count_value;
     }
     // リセットボタンのマウスダウン処理
     reset_btn.onmousedown = function() {
          reset_btn.style.backgroundColor = "#00FF00";
     }
     // リセットボタンのマウスアップ処理
     reset_btn.onmouseup = function() {
          reset_btn.style.backgroundColor = "";
     }
     // オブジェクトと変数の準備
     var count2_disp = document.getElementById("chAnkoDisp");
     var count2_up_btn = document.getElementById("chAnkoUp");
     var reset2_btn = document.getElementById("chAnkoDown");
     var count2_value = 0;
     // カウントアップボタンクリック処理
     count2_up_btn.onclick = function (){
          count2_value += 1;
          count2_disp.value = count2_value;
     };
     // カウントアップボタンのマウスダウン処理
     count2_up_btn.onmousedown = function() {
          count2_up_btn.style.backgroundColor = "#00FF00";
     }
     // カウントアップボタンのマウスアップ処理
     count2_up_btn.onmouseup = function() {
          count2_up_btn.style.backgroundColor = "";
     }
     // リセットボタンのクリック処理
     reset2_btn.onclick = function (){
          count2_value = 0; count2_disp.value = count2_value;
     }
     // リセットボタンのマウスダウン処理
     reset2_btn.onmousedown = function() {
          reset2_btn.style.backgroundColor = "#00FF00";
     }
     // リセットボタンのマウスアップ処理
     reset2_btn.onmouseup = function() {
          reset2_btn.style.backgroundColor = "";
     }
     // オブジェクトと変数の準備
     var count3_disp = document.getElementById("chMinkanDisp");
     var count3_up_btn = document.getElementById("chMinkanUp");
     var reset3_btn = document.getElementById("chMinkanDown");
     var count3_value = 0;
     // カウントアップボタンクリック処理
     count3_up_btn.onclick = function (){
          count3_value += 1;
          count3_disp.value = count3_value;
     };
     // カウントアップボタンのマウスダウン処理
     count3_up_btn.onmousedown = function() {
          count3_up_btn.style.backgroundColor = "#00FF00";
     }
     // カウントアップボタンのマウスアップ処理
     count3_up_btn.onmouseup = function() {
          count3_up_btn.style.backgroundColor = "";
     }
     // リセットボタンのクリック処理
     reset3_btn.onclick = function (){
          count3_value = 0; count3_disp.value = count3_value;
     }
     // リセットボタンのマウスダウン処理
     reset3_btn.onmousedown = function() {
          reset3_btn.style.backgroundColor = "#00FF00";
     }
     // リセットボタンのマウスアップ処理
     reset3_btn.onmouseup = function() {
          reset3_btn.style.backgroundColor = "";
     }
     // オブジェクトと変数の準備
     var count4_disp = document.getElementById("chAnkanDisp");
     var count4_up_btn = document.getElementById("chAnkanUp");
     var reset4_btn = document.getElementById("chAnkanDown");
     var count4_value = 0;
     // カウントアップボタンクリック処理
     count4_up_btn.onclick = function (){
          count4_value += 1;
          count4_disp.value = count4_value;
     };
     // カウントアップボタンのマウスダウン処理
     count4_up_btn.onmousedown = function() {
          count4_up_btn.style.backgroundColor = "#00FF00";
     }
     // カウントアップボタンのマウスアップ処理
     count4_up_btn.onmouseup = function() {
          count4_up_btn.style.backgroundColor = "";
     }
     // リセットボタンのクリック処理
     reset4_btn.onclick = function (){
          count4_value = 0; count4_disp.value = count4_value;
     }
     // リセットボタンのマウスダウン処理
     reset4_btn.onmousedown = function() {
          reset4_btn.style.backgroundColor = "#00FF00";
     }
     // リセットボタンのマウスアップ処理
     reset4_btn.onmouseup = function() {
          reset4_btn.style.backgroundColor = "";
     }
     // オブジェクトと変数の準備
     var count5_disp = document.getElementById("yoMinkoDisp");
     var count5_up_btn = document.getElementById("yoMinkoUp");
     var reset5_btn = document.getElementById("yoMinkoDown");
     var count5_value = 0;
     // カウントアップボタンクリック処理
     count5_up_btn.onclick = function (){
          count5_value += 1;
          count5_disp.value = count5_value;
     };
     // カウントアップボタンのマウスダウン処理
     count5_up_btn.onmousedown = function() {
          count5_up_btn.style.backgroundColor = "#00FF00";
     }
     // カウントアップボタンのマウスアップ処理
     count5_up_btn.onmouseup = function() {
          count5_up_btn.style.backgroundColor = "";
     }
     // リセットボタンのクリック処理
     reset5_btn.onclick = function (){
          count5_value = 0; count5_disp.value = count5_value;
     }
     // リセットボタンのマウスダウン処理
     reset5_btn.onmousedown = function() {
          reset5_btn.style.backgroundColor = "#00FF00";
     }
     // リセットボタンのマウスアップ処理
     reset5_btn.onmouseup = function() {
          reset5_btn.style.backgroundColor = "";
     }
     // オブジェクトと変数の準備
     var count6_disp = document.getElementById("yoAnkoDisp");
     var count6_up_btn = document.getElementById("yoAnkoUp");
     var reset6_btn = document.getElementById("yoAnkoDown");
     var count6_value = 0;
     // カウントアップボタンクリック処理
     count6_up_btn.onclick = function (){
          count6_value += 1;
          count6_disp.value = count6_value;
     };
     // カウントアップボタンのマウスダウン処理
     count6_up_btn.onmousedown = function() {
          count6_up_btn.style.backgroundColor = "#00FF00";
     }
     // カウントアップボタンのマウスアップ処理
     count6_up_btn.onmouseup = function() {
          count6_up_btn.style.backgroundColor = "";
     }
     // リセットボタンのクリック処理
     reset6_btn.onclick = function (){
          count6_value = 0; count6_disp.value = count6_value;
     }
     // リセットボタンのマウスダウン処理
     reset6_btn.onmousedown = function() {
          reset6_btn.style.backgroundColor = "#00FF00";
     }
     // リセットボタンのマウスアップ処理
     reset6_btn.onmouseup = function() {
          reset6_btn.style.backgroundColor = "";
     }
     // オブジェクトと変数の準備
     var count7_disp = document.getElementById("yoMinkanDisp");
     var count7_up_btn = document.getElementById("yoMinkanUp");
     var reset7_btn = document.getElementById("yoMinkanDown");
     var count7_value = 0;
     // カウントアップボタンクリック処理
     count7_up_btn.onclick = function (){
          count7_value += 1;
          count7_disp.value = count7_value;
     };
     // カウントアップボタンのマウスダウン処理
     count7_up_btn.onmousedown = function() {
          count7_up_btn.style.backgroundColor = "#00FF00";
     }
     // カウントアップボタンのマウスアップ処理
     count7_up_btn.onmouseup = function() {
          count7_up_btn.style.backgroundColor = "";
     }
     // リセットボタンのクリック処理
     reset7_btn.onclick = function (){
          count7_value = 0; count7_disp.value = count7_value;
     }
     // リセットボタンのマウスダウン処理
     reset7_btn.onmousedown = function() {
          reset7_btn.style.backgroundColor = "#00FF00";
     }
     // リセットボタンのマウスアップ処理
     reset7_btn.onmouseup = function() {
          reset7_btn.style.backgroundColor = "";
     }
     // オブジェクトと変数の準備
     var count8_disp = document.getElementById("yoAnkanDisp");
     var count8_up_btn = document.getElementById("yoAnkanUp");
     var reset8_btn = document.getElementById("yoAnkanDown");
     var count8_value = 0;
     // カウントアップボタンクリック処理
     count8_up_btn.onclick = function (){
          count8_value += 1;
          count8_disp.value = count8_value;
     };
     // カウントアップボタンのマウスダウン処理
     count8_up_btn.onmousedown = function() {
          count8_up_btn.style.backgroundColor = "#00FF00";
     }
     // カウントアップボタンのマウスアップ処理
     count8_up_btn.onmouseup = function() {
          count8_up_btn.style.backgroundColor = "";
     }
     // リセットボタンのクリック処理
     reset8_btn.onclick = function (){
          count8_value = 0; count8_disp.value = count8_value;
     }
     // リセットボタンのマウスダウン処理
     reset8_btn.onmousedown = function() {
          reset8_btn.style.backgroundColor = "#00FF00";
     }
     // リセットボタンのマウスアップ処理
     reset8_btn.onmouseup = function() {
          reset8_btn.style.backgroundColor = "";
     }
     // オブジェクトと変数の準備
     var count9_disp = document.getElementById("doraDisp");
     var count9_up_btn = document.getElementById("doraUp");
     var reset9_btn = document.getElementById("doraDown");
     var count9_value = 0;
     // カウントアップボタンクリック処理
     count9_up_btn.onclick = function (){
          count9_value += 1;
          count9_disp.value = count9_value;
     };
     // カウントアップボタンのマウスダウン処理
     count9_up_btn.onmousedown = function() {
          count9_up_btn.style.backgroundColor = "#00FF00";
     }
     // カウントアップボタンのマウスアップ処理
     count9_up_btn.onmouseup = function() {
          count9_up_btn.style.backgroundColor = "";
     }
     // リセットボタンのクリック処理
     reset9_btn.onclick = function (){
          count9_value = 0; count9_disp.value = count9_value;
     }
     // リセットボタンのマウスダウン処理
     reset9_btn.onmousedown = function() {
          reset9_btn.style.backgroundColor = "#00FF00";
     }
     // リセットボタンのマウスアップ処理
     reset9_btn.onmouseup = function() {
          reset9_btn.style.backgroundColor = "";
     }
};
</script>

result.ejs

計算結果表示部分。

<style type="text/css">
.box {
}
.item {
	width: 40%;
	float: left
}
</style>

<!--   計算関数読込   -->
<%- include('./calc', data); %>
<!---------------------->

<h1>上がり</h1>
<p>役      :
	<ul>
		<li>1翻:<%= data['1han'] %></li>
		<li>2翻:<%= data['2han'] %></li>
		<li>3翻:<%= data['3han'] %></li>
		<li>4翻:<%= data['4han'] %></li>
		<li>6翻:<%= data['6han'] %></li>
	</ul></p>

<p>上がり    :
<% if (data.agari === 'lon') { %>
ロン
<% } else if (data.agari === 'tsumo') { %>
ツモ
<% } %></p>

<p>親子     :
<% if (data.oyako === 'oya') { %>
親
<% } else if (data.oyako === 'ko') { %>
子
<% } %></p>

<p>鳴き     :
<% if (data.naki === 'ari') { %>
有り
<% } else if (data.naki === 'nashi') { %>
無し
<% } %></p>

<p>面子(中張牌):
	<ul>
		<li>明刻(2符) :<%= data.chMinko %></li>
		<li>暗刻(4符) :<%= data.chAnko %></li>
		<li>明槓(8符) :<%= data.chMinkan %></li>
		<li>暗槓(16符):<%= data.chAnkan %></li>
	</ul></p>

<p>面子(幺九牌):
	<ul>
		<li>明刻(4符) :<%= data.yoMinko %></li>
		<li>暗刻(8符) :<%= data.yoAnko %></li>
		<li>明槓(16符):<%= data.yoMinkan %></li>
		<li>暗槓(32符):<%= data.yoAnkan %></li>
	</ul></p>

<p>ドラ数    :<%= data.dora %></p>

<p>雀頭     :
<% if (data.janto === 'jibasanngen') { %>
自風/場風/三元(2符)
<% } else if (data.janto === 'renpu') { %>
連風(2符)
<% } else if (data.janto === 'sonota') { %>
数/客風(0符)
<% } %></p>

<p>待ち     :
<% if (data.machi === 'penkantanno') { %>
辺張/嵌張/単騎/ノベタン(2符)
<% } else if (data.machi === 'sonota') { %>
両面/シャボ(0符)
<% } %></p>

calc.ejs

計算処理部分。

<h1>計算結果</h1>
<% var fusu = 20; %>
<% var hansu = 0; %>
<% var notenFlag = 0; %>
<% var kuiSagariFlag = 0; %>
<% var kisoten = 0; %>
<% var koHarai = 0; %>
<% var oyaHarai = 0; %>

<!-- 配列数1の時str型になる対応が必要 -->
<!--    上記の場合[1]が一文字になる    -->
<!--              1翻加算             -->
<% if (data['1han']) { %>
<% var hanNum = data['1han'].length; %>
<% if (data['1han'][1].length == 1) { %>
<% hanNum = 1; %>
<% } %>
<% hansu = hansu + hanNum; %>
<% } %>

<!--              2翻加算             -->
<% if (data['2han']) { %>
<% hanNum = data['2han'].length; %>
<% if (data['2han'][1].length == 1) { %>
<% hanNum = 1; %>
<% } %>
<% hansu = hansu + (hanNum * 2); %>
<% } %>

<!--              3翻加算             -->
<% if (data['3han']) { %>
<% hanNum = data['3han'].length; %>
<% if (data['3han'][1].length == 1) { %>
<% hanNum = 1; %>
<% } %>
<% hansu = hansu + (hanNum * 3); %>
<% } %>

<!--              4翻加算             -->
<% if (data['4han']) { %>
<% hanNum = data['4han'].length; %>
<% if (data['4han'][1].length == 1) { %>
<% hanNum = 1; %>
<% } %>
<% hansu = hansu + (hanNum * 4); %>
<% } %>

<!--              6翻加算             -->
<% if (data['6han']) { %>
<% hanNum = data['6han'].length; %>
<% if (data['6han'][1].length == 1) { %>
<% hanNum = 1; %>
<% } %>
<% hansu = hansu + (hanNum * 6); %>
<% } %>

<!-- 食い下がり減算 -->
<% if (data.naki === 'ari') { %>
<!-- 1han -->
<% if (data['1han']) { %>
<% for (var i=0; i<data['1han'].length; i++) { %>
<% if (data['1han'][i] == '立直' || data['1han'][i] == '立') { %>
<% console.log('鳴き有り/立直あり') %>
<% kuiSagariFlag = 1 %>
<% } %>
<% } %>
<% if (kuiSagariFlag >= 1) { %>
<% kuiSagariFlag = 0 %>
<% hansu = hansu - 1 %>
<% } %>
<!-- 1hanおわり -->
<% } %>
<!-- 2han -->
<% if (data['2han']) { %>
<!-- 三色同順 -->
<% for (var i=0; i<data['2han'].length; i++) { %>
<% if (data['2han'][i] == '三色同順' || data['2han'][i] == '三') { %>
<% console.log('鳴き有り/三色同順あり') %>
<% kuiSagariFlag = 1 %>
<% } %>
<% } %>
<% if (kuiSagariFlag >= 1) { %>
<% kuiSagariFlag = 0 %>
<% hansu = hansu - 1 %>
<% } %>
<!-- 混全帯幺九 -->
<% for (var i=0; i<data['2han'].length; i++) { %>
<% if (data['2han'][i] == '混全帯幺九' || data['2han'][i] == '混') { %>
<% console.log('鳴き有り/混全帯幺九あり') %>
<% kuiSagariFlag = 1 %>
<% } %>
<% } %>
<% if (kuiSagariFlag >= 1) { %>
<% kuiSagariFlag = 0 %>
<% hansu = hansu - 1 %>
<% } %>
<!-- 一気通貫 -->
<% for (var i=0; i<data['2han'].length; i++) { %>
<% if (data['2han'][i] == '一気通貫' || data['2han'][i] == '一') { %>
<% console.log('鳴き有り/一気通貫あり') %>
<% kuiSagariFlag = 1 %>
<% } %>
<% } %>
<% if (kuiSagariFlag >= 1) { %>
<% kuiSagariFlag = 0 %>
<% hansu = hansu - 1 %>
<% } %>
<!-- 2hanおわり -->
<% } %>
<!-- 3han -->
<% if (data['3han']) { %>
<!-- 純全帯幺九 -->
<% for (var i=0; i<data['3han'].length; i++) { %>
<% if (data['3han'][i] == '純全帯幺九' || data['3han'][i] == '純') { %>
<% console.log('鳴き有り/純全帯幺九あり') %>
<% kuiSagariFlag = 1 %>
<% } %>
<% } %>
<% if (kuiSagariFlag >= 1) { %>
<% kuiSagariFlag = 0 %>
<% hansu = hansu - 1 %>
<% } %>
<!-- 混一色 -->
<% for (var i=0; i<data['3han'].length; i++) { %>
<% if (data['3han'][i] == '混一色' || data['3han'][i] == '混') { %>
<% console.log('鳴き有り/混一色あり') %>
<% kuiSagariFlag = 1 %>
<% } %>
<% } %>
<% if (kuiSagariFlag >= 1) { %>
<% kuiSagariFlag = 0 %>
<% hansu = hansu - 1 %>
<% } %>
<!-- 3hanおわり -->
<% } %>
<!-- 6han -->
<% if (data['6han']) { %>
<!-- 清一色 -->
<% for (var i=0; i<data['6han'].length; i++) { %>
<% if (data['6han'][i] == '清一色' || data['6han'][i] == '清') { %>
<% console.log('鳴き有り/清一色あり') %>
<% kuiSagariFlag = 1 %>
<% } %>
<% } %>
<% if (kuiSagariFlag >= 1) { %>
<% kuiSagariFlag = 0 %>
<% hansu = hansu - 1 %>
<% } %>
<!-- 6hanおわり -->
<% } %>
<!-- 食い下がり減算おわり -->
<% } %>

<!-- ノーテン判定 -->
<% if (hansu <= 0) { %>
<% notenFlag = 1 %>
<% } %>

<!-- ドラ加算 -->
<% if (data.dora != 0) { %>
<% hansu = hansu + Number(data.dora) %>
<% } %>

<!-- 上り符数判定  -->
<% if (data.agari === 'lon' && data.naki === 'nashi') { %>
<% fusu = fusu + 10 %>
<% } else if (data.agari === 'tsumo') { %>
<% fusu = fusu + 2 %>
<% } %>

<!-- 面子(中張牌)符数判定  -->
<% if (data.chMinko != 0) { %>
<% fusu = fusu + (data.chMinko * 2) %>
<% } %>
<% if (data.chAnko != 0) { %>
<% fusu = fusu + (data.chAnko * 4) %>
<% } %>
<% if (data.chMinkan != 0) { %>
<% fusu = fusu + (data.chMinkan * 8) %>
<% } %>
<% if (data.chAnkan != 0) { %>
<% fusu = fusu + (data.chAnkan * 16) %>
<% } %>

<!-- 面子(幺九牌)符数判定  -->
<% if (data.yoMinko != 0) { %>
<% fusu = fusu + (data.yoMinko * 4) %>
<% } %>
<% if (data.yoAnko != 0) { %>
<% fusu = fusu + (data.yoAnko * 8) %>
<% } %>
<% if (data.yoMinkan != 0) { %>
<% fusu = fusu + (data.yoMinkan * 16) %>
<% } %>
<% if (data.yoAnkan != 0) { %>
<% fusu = fusu + (data.yoAnkan * 32) %>
<% } %>

<!-- 雀頭符数判定  -->
<% if (data.janto === 'jibasanngen') { %>
<% fusu = fusu + 2 %>
<% } %>
<% if (data.janto === 'renpu') { %>
<% fusu = fusu + 2 %>
<% } %>

<!-- 待ち符数判定  -->
<% if (data.machi === 'penkantanno') { %>
<% fusu = fusu + 2 %>
<% } %>

<!-- 符丸め -->
<% fusu = (Math.ceil(fusu / 10) * 10) %>
<!--  子  -->
<% if (data.oyako === 'ko') { %>
<% kisoten = fusu * 4 * (2 ** (hansu + 2)) %>
<% } %>
<!--  親  -->
<% if (data.oyako === 'oya') { %>
<% kisoten = fusu * 6 * (2 ** (hansu + 2)) %>
<% } %>
<!--  丸め  -->
<% kisoten = (Math.ceil(kisoten / 100) * 100) %>

<!-- 0翻判定 -->
<% if (hansu <= 0) { %>
<% kisoten = 0 %>
<% } %>

<!-- 満貫以上判定(子) -->
<% if (data.oyako === 'ko') { %>
<% if (hansu == 3 && fusu >= 70) { %>
<% kisoten = 8000 %>
<% } %>
<% if (hansu == 4 && (40 <= fusu && fusu <= 60)) { %>
<% kisoten = 8000 %>
<% } %>
<% if (hansu == 5) { %>
<% kisoten = 8000 %>
<% } %>
<% if (hansu == 6 || hansu == 7) { %>
<% kisoten = 12000 %>
<% } %>
<% if (8 <= hansu && hansu <= 10) { %>
<% kisoten = 16000 %>
<% } %>
<% if (11 <= hansu && hansu <= 12) { %>
<% kisoten = 24000 %>
<% } %>
<% if (13 <= hansu) { %>
<% kisoten = 32000 %>
<% } %>
<% } %>

<!-- 満貫以上判定(親) -->
<% if (data.oyako === 'oya') { %>
<% if (hansu == 3 && fusu >= 70) { %>
<% kisoten = 12000 %>
<% } %>
<% if (hansu == 4 && (40 <= fusu && fusu <= 60)) { %>
<% kisoten = 12000 %>
<% } %>
<% if (hansu == 5) { %>
<% kisoten = 12000 %>
<% } %>
<% if (hansu == 6 || hansu == 7) { %>
<% kisoten = 18000 %>
<% } %>
<% if (8 <= hansu && hansu <= 10) { %>
<% kisoten = 24000 %>
<% } %>
<% if (11 <= hansu && hansu <= 12) { %>
<% kisoten = 36000 %>
<% } %>
<% if (13 <= hansu) { %>
<% kisoten = 48000 %>
<% } %>
<% } %>

<!-- 表示用計算(親) -->
<% if (data.oyako === 'oya') { %>
<% if (data.agari === 'tsumo') { %>
<% koHarai = kisoten / 3; %>
<% oyaHarai = ''; %>
<% } else if (data.agari === 'lon') { %>
<% koHarai = kisoten; %>
<% oyaHarai = ''; %>
<% } %>
<% } %>
<%  %>
<!-- 表示用計算(子) -->
<% if (data.oyako === 'ko') { %>
<% if (data.agari === 'tsumo') { %>
<% koHarai = kisoten / 4; %>
<% oyaHarai = kisoten / 2; %>
<% } else if (data.agari === 'lon') { %>
<% koHarai = kisoten; %>
<% oyaHarai = ''; %>
<% } %>
<% } %>

<!--  丸め  -->
<% koHarai = (Math.ceil(koHarai / 100) * 100) %>
<% oyaHarai = (Math.ceil(oyaHarai / 100) * 100) %>

<% if (notenFlag == 1) { %>
<% koHarai = 0 %>
<% oyaHarai = 0 %>
<% } %>

<% console.log(hansu) %>
<% console.log(fusu) %>
<% console.log(kisoten) %>
<% console.log(koHarai) %>
<% console.log(oyaHarai) %>
<% console.log(data) %>

<!-- 表示 -->
<div class="box">
        <div class="item">
		<%= hansu %>翻
        </div>
        <div class="item">
		<%= fusu %>符
        </div>
</div>
<div class="box">
        <div class="item">
		<%= koHarai %>
        </div>
        <div class="item">
		<%= oyaHarai %>
        </div>
</div>
<br><br>

コンテナ実行

以上でNode.jsを使ったコーディングは終了です。次はコンテナを立ち上げます。
※後で自動化します

$ docker build -t nodejs .
$ docker run -itd --name nodejs --privileged -p 3000:3000 nodejs /sbin/init
$ docker exec nodejs node /myapp/index.js

問題が無ければ麻雀点数計算を行うWebアプリが立ち上がっていると思います。

CloudFormationテンプレ作成

冒頭でお話した通りCloudFormationを使ってAWS周辺の自動化を行います。CloudFormationはymlかJSON形式で記述します。

# Template Version
AWSTemplateFormatVersion: "2010-09-09"

# Description for Template
Description: Provision EC2

# Input Parameters
Parameters:
  KeyName:
    Description: The EC2 Key Pair to allow SSH access to the instance
    Type: "AWS::EC2::KeyPair::KeyName"

# Resources Template
Resources:
  EC2: 
    Type: AWS::EC2::Instance
    Properties: 
      ImageId: ami-06a46da680048c8ae
      KeyName: !Ref KeyName
      InstanceType: t2.micro
      SecurityGroupIds:
        - sg-087cf2a6e41c93abd
      UserData: !Base64 |
        #!/bin/bash
        sudo yum -y update; yum -y install git docker; systemctl start docker;
        git clone 【GitHubリポジトリ】
        cd 【cloneしたディレクトリ】
        sudo docker build -t nodejs .
        docker run -itd --name nodejs --privileged -p 3000:3000 nodejs
        docker exec nodejs node /myapp/index.js &
      Tags:
          - Key: Name
            Value: labo_nodejs

# Outputs Parameters
Outputs:
  EC2PublicIP:
    Value: !GetAtt EC2.PublicIp
    Description: Public IP of EC2 instance
  • 8~11行目:
    • 使用するキーペアのパラメータ
    • ※実行する時に入力する
  • 18行目:
    • 最新版CentOS7のAMI-ID
  • 21~22行目:
    • 作成済みセキュリティグループ指定
    • インバウンドにSSHと3000/TCPを許可
  • 23~30行目:
    • 起動後に実行するコマンド群

この記事に興味を持って読んで下さった人なら分かるかと思いますのでGitHub操作の説明は割愛しております。分からないという人でも、過去にGitHub記事をいくつか書いておりますのでご参照頂ければ理解できるはずです。

CloudFormation実行

CloudFormationを実行するにはS3にアップロードしておくか、実行する直前にローカルからアップロードするか2択あります。どちらでも問題無いです。

  1. まず「サービス」から「CloudeFormation」に飛んでください
  2. 「スタックの作成」をクリック
  3. S3かローカルからテンプレートを選択
  4. スタック名/パラメータを入力
  5. 必要なら「タグ」にNameと値を入力
  6. レビューを見て問題なければ「スタックの作成」をクリック

しばらく待つとインスタンスが作成されて、払い出されたIPのURIにアクセスするとアプリがデプロイされているかと思います。以上です。

終わりに

全自動麻雀卓欲しい……

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA