技術ブログ | 株式会社アイプランニング IPlanning corporation

アイプランニング社員が調査したこと、学んでいることが具体的にどんなものなのかを披露します。 Here is what the IPlanning corp employees surveyed and what they learned.

scoopを使って開発用PCをセットアップする

インターンシップの開催にあたり、PCの開発環境のセットアップを手順化しました。

セットアップ対象

セットアップするツール一覧

Scoopでのインストール

セットアップはscoopで行います。scoopを使う理由としては、管理者権限を必要としないためです。 (別のオプションとしてchocolateyを使うこともできます)

https://scoop.sh/

まず、Powershellを起動します。Win + Rからファイル名を指定して実行、powershellを入力すると起動します。

下記のコマンドをコピーし、Powershellウィンドウにペーストします。 (右クリックでペーストすることができます) 改行で実行してください。

Set-ExecutionPolicy RemoteSigned -scope CurrentUser

f:id:iplcojp:20191118140851p:plain

Powershellスクリプトを実行するための権限変更の確認が入りますので、yを押して承諾します。

これでscoopがインストール可能になります。 その後、下記を実行します。

iwr -useb get.scoop.sh | iex

scoopがインストールされます。 scoopのインストールが完了したら、下記のコマンドをすべてコピーし、実行してください。

scoop install git
scoop install nodejs
scoop install yarn

これでNode.js関連のツールがすべてインストールされました。

インストーラでのインストール

VSCodeは、公式インストーラを使ってインストールしてください。 (scoopでのインストールも可能ですが、今回はコンテキストメニューに「Codeで開く」を追加したいため)

code.visualstudio.com

f:id:iplcojp:20191118143326p:plain

インストールオプションで「ショートカットを追加」「コンテキストメニューに追加」をそれぞれチェックしてください。

その後、Chromeもインストールを行います。

www.google.co.jp

以上でセットアップは完了です。

VSCodeの日本語化&拡張機能インストール

デスクトップに配置されたVisual Studio Codeを起動します。 このままだと英語なので、日本語化しましょう。

拡張機能アイコンをクリックし、japaneseと入力して検索します。

f:id:iplcojp:20191118152043p:plain

Japanese Language Packが表示されますので、installを選択して下さい。

f:id:iplcojp:20191118152224p:plain

再起動が促されますので、Restart Nowを選択します。

Live Serverのインストール

HTMLをプレビューするために、Live Server拡張をインストールします。

拡張機能から、liveserverと入力し、Live Serverと表示されたらインストールしてください。

f:id:iplcojp:20191118152427p:plain

以上で、VSCode設定完了です。

Vue.js で麻雀聴牌判定ゲームを作る

f:id:iplcojp:20180731135837p:plain

2年目若手が新人研修のときに作ったC#の麻雀ゲームを Web版として移植してもらいました。

ゲームはスマートフォンでもプレイできるようになっておりますので、 ぜひ遊んでみてください!

フレームワークにはVue.jsを利用しています。
また、聴牌判定ロジックにこだわりがあるとのことで、 ロジックについても解説記事を書いてもらいました。

ゲームを遊ぶ

清一色の練習をするためのゲームです。

こちらで遊べます。

Chromeで動作確認を行っております。IEなどのレガシーブラウザでは動作いたしません。

作成物概要

f:id:iplcojp:20180731144406p:plain f:id:iplcojp:20180731135837p:plain

麻雀とは

34 種 136 牌を使用するゲームです。手牌 13 枚から始まり、1 枚引いては捨てるを繰り返して和了(あがり)を競います。細かいルールは記載しきれないので省略します。

f:id:iplcojp:20180731135848p:plain

画像拝借元

用語

手牌(てはい): 各プレイヤーの所有する牌。基本的に 13 枚。
雀頭(じゃんとう): 同じ牌が 2 枚の組。
面子(めんつ): 同じ牌が 3 つの組、又は連続した数字が 3 つ揃ったもの。
和了(あがり): 雀頭 1 つ、面子 4 つを揃えた形。
聴牌(てんぱい): あと 1 枚で和了形になる状態。
待ち牌(まちはい): 聴牌の状態から和了するための牌。待ち牌の個数で 1 面待ち、2 面待ちなどと表現する。
清一色(ちんいつ、ちんいーそー): 和了形が 1 色の牌のみで構成されているもの。

和了

f:id:iplcojp:20180731135936p:plain

聴牌

f:id:iplcojp:20180731135944p:plain

清一色聴牌

待ち牌を探すのが難しいのが特徴です。

f:id:iplcojp:20180731135953p:plain

作成背景

麻雀の牌の組み合わせに興味があったためです。

清一色には 1~9 面待ちの手牌の組が存在します。

n 面待ちは何通りあるのか、n 面待ちをランダムに表示して遊ぶにはどうするのがよいか、 というような思いから作成に至りました。

計算ロジック

聴牌の判定方法や、手牌をどう格納するかといったことを記述してあります。

聴牌判定ロジック

13 枚の手牌に 1~9 の牌を加えて、それが和了の形となっているか判定する方法を使っています。

雀頭となりうる牌を除外して、他の牌が全て面子の形となっていれば OK です。

雀頭となりうる牌*2 ≡ 14 枚の牌の合計値 (mod 3) という性質も利用することで、雀頭候補を絞れて無駄な計算を省けます。

n 面待ちランダム取得ロジック

予め 1~9 面待ちの全ての手牌の状態を 2 次元リストに格納しておき、定数時間で n 面待ち k 種目の手牌を取り出せるようにしました。

「n 面待ちが出るまで手牌をランダムに生成し続ける方法はダメなのか?」という点については、 1,2 面待ち程度なら大丈夫ですが、7,8 面待ちになると時間がかかってしまうときがあります。 下の図は 13 枚の手牌が 1 色に染まっているときの n 面待ちの組み合わせ(種類)と個数です。

待ち数 種類 確率%(種類) 個数 確率%(個数)
ALL 93,600 100.000 2,310,789,600 100.000
0 53,404 57.056 1,188,283,736 51.423
1 14,193 15.163 366,605,432 15.865
2 14,493 15.484 413,556,512 17.897
3 6,739 7.200 225,438,896 9.756
4 2,948 3.150 68,889,152 2.981
5 1,335 1.426 32,848,640 1.422
6 392 0.419 12,497,408 0.541
7 79 0.084 2,176,256 0.094
8 16 0.017 231,424 0.010
9 1 0.001 262,144 0.011

この図より、ランダムに手牌を生成して 7,8 面待ちを得るためには、1 万回ほどの試行を必要とするのがわかります。 運が悪いときは 10 万回手牌を生成しても得られないときがあります。

「手牌リストに格納した場合、メモリ使用量は大丈夫か?」という疑問が湧きますが、これについては400KB あれば足ります。先ほどの図から、種類は全部で 93600 通りです。手牌の状態は 4byte で表現可能(後述)なため、使用するメモリ(概算)は 93600*4 ≒ 365KB で済みます。

もし仮に 1,2 面待ちの種類が多く、メモリを占有しすぎてしまう場合は、3~9 面待ちだけをリストに格納して、他はランダムに生成に期待する方法が良いと思います。

手牌の格納方法

13 枚の牌を表現する方法です。副露した牌については考慮していませんので注意してください。

  1. 純粋にリストで表現
    手牌の i 番目の数字を list[i]に格納するシンプルな方法です。必要なリストのサイズは 13 です。
  2. 各牌の個数をリストで格納
    数字が i の牌の個数を list[i]に格納する方法です。必要なリストのサイズは 9 ですが、手牌の並びの情報は失われます。
  3. 各牌の個数を int 型(32bit)に詰める
    方法 2 の各牌の個数をリストで格納する方法において、list に格納される数字は 0~4 のみで、使用する bit サイズが 3 であるのがわかります。そのため、1~3bit 目に 1 の牌の個数、4~6bit 目に 2 の牌の個数 ... 25~27bit 目に 9 の牌の個数とすれば、int 型で格納できることになります。本 Web アプリではこの格納方法を使っています。
  4. 手牌+待ち牌を int 型で表現
    方法 3 とは別のやり方で、ソート済の手牌を表現でき、且つ使用 bit 領域を 21 にまで減らせます。 牌の個数分 1 を並べて、数字の区切り部分を 0 とすれば OK です。例えば 1112355666889 という手牌についてみてみます。数字の区切りを"|"で表すと 111|2|3||55|666||88|9 となります。数字を全て 1 に、"|"を 0 に変換すると 111010100110111001101 になります。
    待ち牌は 9bit あれば表現可能(その牌の番号の bit を立てる)です。残りの 11bit 部分を使えば待ち牌の情報も加わった手牌を int 型で表せます。

ソースコードの一部を掲載します。

const TILE_SUM_BLOCK_BIT = 3; //同じ牌の合計を記録するためのbit領域
const TILE_SUM_MASK = 0b111;  //同じ牌の合計を記録するためのbit領域(mask用)

class TenpaiJudge {
  constructor() {
  }
  //[0, 1, 4, 1, 1] のような手牌をsumTile形式にする(0~2bit目で牌1の個数、3~5bit目で牌2の個数、・・・24~26bit目で牌9の個数)
  // 同じ牌が5個以上ある場合はreturn -1
  toSumTile(aryTile) {
    let sumTile = 0;
    let sumTileList = []; //i+1の牌が何個あるか
    for (let i = 0; i < 9; i++) sumTileList.push(0);
    for (let i = 0; i < aryTile.length; i++) {
      let tileNum = aryTile[i];
      sumTileList[tileNum]++;
      if (sumTileList[tileNum] > 4) return -1;
      sumTile += 1 << (tileNum * TILE_SUM_BLOCK_BIT);
    }
    return sumTile;
  }

  //sumTileは 3bitを1ブロックとして、
  //0~2bit目で牌1の個数、3~5bit目で牌2の個数、・・・24~26bit目で牌9の個数を表す
  //戻り値は和了牌、和了可能bitを立てる(牌1で和了可のときは0bit目、・・・9で和了可のときは8bit目を立てる)
  calcFullFlashWinTile(sumTile) {
    //筋合計mod3算出
    let sujiMod3 = [0, 0, 0];
    for (let suji = 0; suji < 3; suji++) {
      let sujiSum = 0;
      for (let shiftBlock = suji; shiftBlock < 9; shiftBlock += TILE_SUM_BLOCK_BIT) {
        sujiSum += (sumTile >> (shiftBlock * TILE_SUM_BLOCK_BIT)) & TILE_SUM_MASK;

      }
      sujiMod3[suji] = sujiSum % 3;
    }

    //牌の数字合計mod3
    let numSumMod3 = (sujiMod3[1] + sujiMod3[2] * 2) % 3;

    let winTile = 0;//和了牌
    //sumTileにmayWintileを加えて和了形か判定
    //手牌14枚に対し、頭mod3は必然に決まる
    for (let mayWintile = 0; mayWintile < 9; mayWintile++) {
      //加える牌が5枚目のときはスルー
      if (((sumTile >> (mayWintile * TILE_SUM_BLOCK_BIT)) & TILE_SUM_MASK) === 4) continue;

      let addedSumTile = sumTile + (1 << (TILE_SUM_BLOCK_BIT * mayWintile));
      let headTileMod3 = (12 - numSumMod3 - mayWintile) % 3;
      for (let headTile = headTileMod3; headTile < 9; headTile += 3) {
        // 頭候補の牌が2枚未満のとき, 何もしない
        if (((addedSumTile >> (headTile * TILE_SUM_BLOCK_BIT)) & TILE_SUM_MASK) < 2) continue;
        //14牌から頭を抜く
        let addedSumTile_remove_head = addedSumTile - (2 << (headTile * TILE_SUM_BLOCK_BIT));
        if (this.isMentu(addedSumTile_remove_head)) {
          winTile |= 1 << mayWintile;
          break;
        }
      }
    }

    //和了牌無なら七対子判定
    if (winTile === 0) {
      //0枚牌、1枚牌、・・・4枚牌は何個か
      let nPerSum = [0, 0, 0, 0, 0];
      let mayWintile;
      for (let i = 0; i < 9; i++) {
        let maisuu = (sumTile >> (i * TILE_SUM_BLOCK_BIT)) & TILE_SUM_MASK;
        nPerSum[maisuu]++;
        if (maisuu === 1) mayWintile = i;
      }
      if (nPerSum[1] === 1 && nPerSum[2] === 6) winTile |= 1 << mayWintile;
    }
    return winTile;
  }

  //端牌から消していく、sumTileもシフトする
  isMentu(sumTile) {
    while (sumTile > 0) {
      let termSum = sumTile & TILE_SUM_MASK;
      if (termSum === 0 || termSum === 3) {
        sumTile >>= TILE_SUM_BLOCK_BIT;
        continue;
      }
      let nextSum = (sumTile >> TILE_SUM_BLOCK_BIT) & TILE_SUM_MASK;
      let afterNextSum = (sumTile >> (TILE_SUM_BLOCK_BIT * 2)) & TILE_SUM_MASK;
      if (termSum === 2) {
        if (nextSum >= 2 && afterNextSum >= 2) sumTile -= (2 << TILE_SUM_BLOCK_BIT) + (2 << (TILE_SUM_BLOCK_BIT * 2));
        else return false;
      }
      // if (termSum === 1 || termSum === 4)
      else {
        if (nextSum >= 1 && afterNextSum >= 1) sumTile -= (1 << TILE_SUM_BLOCK_BIT) + (1 << (TILE_SUM_BLOCK_BIT * 2));
        else return false;
      }
      sumTile >>= TILE_SUM_BLOCK_BIT;
    }
    return true;
  }
}

Vue.jsについて

今回はVue.jsで初めてアプリを作成するということもあり、CDN版Vue.jsを利用することを選択しました。

アプリケーションの分割方法は、 一般的に推奨されているSingle File Componentは利用せず、 一つのコンポーネントを一つのJSファイルに記述する方式を選択しました。

基本的には下記のように記述します。

Vue.component("my-component", {
  template: `
  <div>
    <!--  ここにテンプレートを記載する -->
  </div>
  `,
  methods: {
    //TODO
  },
  mounted() {
    //TODO
  },

  data() {
    return {
      // TODO
    }
  }
})

以上のように記載したテンプレートを、new Vue()をしているスクリプトより前に読み込むことで、コンポーネントを利用することができます。

  <script src="src/my-component.js"></script>

この方式の利点としては、下記が挙げられます。

  • index.htmlの肥大化が防げる
  • プレーンなJavaScriptとして記述できること
  • テンプレートとスクリプトを同じファイル内に書くと、Single File Componentと似た構成になるため、SFC採用時に移行がスムーズになること

結果

Vue を使うことで画像表示やイベント処理を手軽に記述できました。

こちらで遊べます。

スマートフォンでは横画面でのプレイを推奨します。

ソースコード

ここからダウンロードできます

感想

計算ロジックにこだわりすぎて、見た目や使いやすさに力を入れられていないです。あと Vue は便利です。

SVGとVue.jsでグラフコンポーネントを作成する

一年目の若手エンジニアに、データビジュアライゼーションのトレーニングを兼ねて、SVGでグラフを作ってもらいました。

作成物概要

f:id:iplcojp:20180326113827p:plain

総務省統計局の平成29年11月時点の人口データを元に各年齢ごとの人口を男女別にグラフを作成しました。

横軸に年齢(5歳ごとの分類)、縦軸に人数(単位:千人)で男女別で表示しています。
男性を青、女性を赤、合計を灰色に設定しています。

また、縦軸最大値と人口の最大値をあわせたり、グラフの大きさをウィンドウサイズ丁度に表示されるように作成しました。

SVGの基本的な使い方

SVGタグの中にSVG要素を書いていきます。具体的には以下の通りです。

<svg viewbox="0 0 400 500" width=400 height=500>
  <!-- ここに書きたい物を書いていく -->
</svg>

viewboxとは?

描画領域を"x y width height"で指定する事が出来ます。

xとyで描画位置を指定(描画領域の左上部分というイメージ)、widthとheightで長さを決定します。

SVG内に描画出来る要素

  • circle(円)
    • 中心座標(cx,cy)と半径rを指定して作成する
  • ellipse(楕円)
    • 中心座標(cx,cy)とx方向への長さrx、y方向への長さryを指定して作成する
  • rect(四角形)
    • 座標x,yから横にwidth、縦にheight分伸ばして作成する
      • rx,ry(楕円と同じような考え)で指定も可能
  • line(直線)
    • (x1,y1)と(x2,y2)の2つの座標から直線を作成する
  • polyline(連続した直線)
    • pointsを指定して作成。 "x1 y1 x2 y2 ... xn yn"
      • points内の要素が偶数になるよう注意、要素が奇数になると最後のy座標の値がなく、バグの原因になる。
  • polygon(多角形)
    • polylineと同様pointsを指定して作成
      • 始点と終点座標を一緒にする必要はない(始点と終点に直線が描画され多角形になる)
  • text(テキスト)
    • 座標(x,y)にフォント名(font-family)と文字の大きさ(font-size)を指定して作成
      • text-anchorで文字の揃え方(左、右、中央揃え)やdominant-baselineで高さ位置の指定なども可能
  • path(パス、曲線)
    • 始点と終点を設定する。その間に制御点を加えることでベジエ曲線を描く事が可能
      • 大体の図形はpathで描けるが、複雑になりやすいため簡単な図形は上記の要素を書いた方がいい

主要な要素は上記の通りですが、他にも様々な要素を使うことが出来ます。詳しくはMDNのリファレンスをチェックして下さい。

注意事項

  • svg要素内では、html要素は基本的に使えなくなります
    • divでグループ分け出来ないため、g要素でグループ分けを行います。
      • title属性を指定して、ツールチップの表示を試みましたが、不可能でした。
    • HTML要素をどうしても使いたい場合は、foreignObject要素内で利用します。
      • 折り返しテキストを表現する用途でよく使われます。参考サイト

作成物

https://iplanning.heteml.net/2018-svg/

  • 上記ページで閲覧する事ができます。

グラフ描画部の実装ポイント

棒グラフの描画部は下記のようになっています。

<g v-for="(data, index) in datas" :key="index">
  <rect class="totalPopulation" :x="setGraphX(index)" :y="setGraphY(data[0])" :width="rectScaleWidth()" :height="rectScaleHeight(data[0])"></rect>
  <rect class="malePopulation" :x="setGraphX(index)" :y="setGraphY(data[1])" :width="rectScaleWidth()" :height="rectScaleHeight(data[1])"></rect>
  <rect class="femalePopulation" :key="index" :x="setGraphX(index)" :y="setGraphY(data[2])" :width="rectScaleWidth()" :height="rectScaleHeight(data[2])"></rect>
  <text class="axisTextX" v-if="textDataX(index)" :x="setGraphX(index)" :y="graphHeight">{{textDataX(index)}}</text>
</g>

x, y, width, heightを計算する部分は、methods内に定義します。

methods: {
  setGraphX (index) {
    return (index * this.graphWidth / this.datas.length)
  },
  setGraphY (data) {
    return this.graphHeight - data / this.maxData * this.graphHeight
  },
  rectScaleWidth () {
    return this.graphWidth / this.datas.length
  },
  // グラフ横幅
  rectScaleHeight (data) {
    return data / this.maxData * this.graphHeight
  },  
  ...
}

maxDataは表示データの最大値で、これをもとにデータ表示範囲を自動的に調整しています。

おまけ

f:id:iplcojp:20180327173540p:plain

  • 作成物内にポケモンステータスをグラフに示したものも合わせて作成しました。
    • ポケモン種族値から2種類データを選択し、x,y軸に対応させて表示してあります。
    • タイプごとの表示やマウスカーソルを当てたときにデータが表示される機能などを実装しました。
    • 表示データはpokedexの公開データ(MIT)を利用しました。

おまけの実装ポイント

グラフ表示に加え、データの絞込や軸ごとの表示データの選択機能も付けました。 f:id:iplcojp:20180327175330p:plain

グラフ表示部は下記のような実装になっています。

<g v-for="data in datas">
  <g v-for="type in data.type">
    <g v-if="selectType === 'all'|selectType === type|selectType2 === type" @mouseenter="pokemonView(data)">
      <circle :class="type" :cx="setGraphX(data)" :cy="setGraphY(data)" :r="circleScale" :name="data.name" />
    </g>
  </g>
</g>

計算部は下記の通りです。マウスカーソルを重ねた時には、該当するポケモンの名称やパラメータを画面下部に表示します。

methods: {
  setGraphX (data) {
    return data[this.graphStats.x] * this.graphWidth / this.maxX
  },
  setGraphY (data) {
    return this.graphHeight - data[this.graphStats.y] * this.graphHeight / this.maxY
  },
  pokemonView (data) {
    this.viewStr = '名前: ' + data.name + ' ' + this.status[this.graphStats.x] + ' : ' + data[this.graphStats.x] + ' ' + this.status[this.graphStats.y] + ': ' + data[this.graphStats.y]
    return
  }
},

終わりに

特別なグラフライブラリを使用せず、Vue.jsとSVGのみでグラフを構築しました。

絞込UIに関してもVue.jsで実装できるため、UIとグラフの連携もスムーズになるなどの利点があるかと思います。

参考ページ

developer.mozilla.org

Java製のシューティングゲームをTypeScriptでWebに移植する

f:id:iplcojp:20180302203529g:plain

10年前にIPL社員が作ったJava製のシューティングゲームを、Web技術を使ってリメイクしました。 また、JavaからTypeScriptへの移植のポイントについても記載します。

何はともあれゲームを遊ぶ

以下のURLからプレイできます。

https://iplanning.heteml.net/2018-shooting/

Chrome, Firefox, Edgeブラウザで動作確認しております。スマートフォンには非対応です。

操作方法

スペースキー:ゲーム開始&弾を撃つ
カーソルキー:上下左右に移動

ハイスコアはお使いのブラウザ内(LocalStorage)に記録されます。

経緯

アイプランニングでは、毎年新人に自由課題を課しています。

ゲームやカロリー計算機など、人によって作るものはそれぞれですが、 10年前に筆者は、当時それなりに新しい技術だったJavaとAWT(当時既にSwingはありましたが)を使って、 簡単なシューティングゲームを作りました。

旧版はこちらで記事にしています。

見た目は下記のような、かなり寂しいものになっています。

f:id:iplcojp:20180302194949j:plain

10年の時を経て、今やJavaScriptの方が当時のJavaよりも高速に動作する時代です。 JavaScriptはブラウザなどのマルチプラットフォーム*1で動作するという利点があるため、 弊社でもJavaからJavaScriptへの移植案件を度々頂くことがあります。

一方で、JavaScriptは良くも悪くも柔軟なところがあり、バグを作り込みやすく、大規模な開発には向いていません。

これに対処するため、弊社で採用している言語が「TypeScript」です。

TypeScriptとは

www.typescriptlang.org

TypeScriptは、コンパイルするとJavaScriptを出力する言語です。 (このような言語を一般的にAltJSと呼びます。同種の言語として、ElmやFlowなども人気です)

TypeScriptの最も強力で便利な機能は、型チェックです。

TypeScriptでは、コンパイル時に型チェックを行なうことが出来ます。 最も簡単なサンプルを下記に例示します。

f:id:iplcojp:20180305202844p:plain

例示したコードでは、name引数にstring型を指定しています。 ここで、name引数に数値を指定すると、型が想定と異なるために、コンパイル時にエラーになります。 また、namenullundefinedが来ることも防ぐ事が出来ます。

このTypeScriptコンパイラの強力な型チェック機能により、開発者はより安全なコードを書くことが出来ます。

移植作業は、大きなひとかたまりのコードを移植して、エラーを直す作業の繰り返しなので、賢いコンパイラは非常に頼りになる存在です。

TypeScriptはJava開発者におすすめ

アイプランニングには、多くのJava経験者が在籍しています。 その為、Java経験者にとって学習が容易である点も重要です。

その点、TypeScriptはJavaからの移行もスムーズです。理由としては下記が挙げられます。

  • TypeScriptはC#に似ています*2が、C#に近い言語であるJavaにもある程度文法が似ています。
  • クラス*3やインターフェースがあるため、Javaのフィーリングで書くことが出来ます。
  • VSCodeを使うことで、Eclipseのような賢い補完やリファクタ機能を使う事ができます*4

ただし、下記の点について注意が必要です。

  • TypeScriptは型指定が後置です。
  • TypeScriptはStructural Subtyping(構造的部分型) を採用しています。
    • 型の構造が一致していれば派生型として扱われます。基本的にJavaは型に対して厳格ですが、TypeScriptの場合はより柔軟です。これはJavaからTypeScriptへの移植は簡単ですが、TypeScriptからJavaへの移植は難しいことを意味しています。
  • 計算精度について注意が必要です。

移植の方法

JavaからTypeScriptへのコンバータは知る限り存在しません。*5

ということで、ひたすら手で書き換えていきます。対象クラスは13個です。

f:id:iplcojp:20180305211013p:plain

他のクラスに依存していない、例えばデータクラスの移植を最初に行なうと良いでしょう。一括変換などは現実的ではありません。エラーが出ないことを確認しながら少しずつ置き換える作業になります。

また、作業量を削減するため、最初はany型を許可する設定(noImplicitAnyを指定しない)にしておきましょう。*6

例として、プレイヤークラスの移動処理を掲載します。

移植前(Java

   /**
    * 移動処理
    * @param mx x方向の入力(-1 ... +1)
    * @param my y方向の入力(-1 ... +1)
    */
    public void move(int mx, int my)
    {
        //Canvasの外には移動できないようにする
        double postX = x + mx * speed;
        double postY = y + my * speed;
        
        if ((0 < postX)&&(postX < 500))
        {
            x = postX;
        }
        if ((0 < postY)&&(postY < 480))
        {
            y = postY;
        }
    }

移植後(TypeScript)

  movePlayer(mx: number, my: number) {
    //Canvasの外には移動できないようにする
    var postX: number = this.x + mx * this.speed;
    var postY: number = this.y + my * this.speed;

    var margin = 50
    if (0 + margin < postX && postX < 500- margin) {
      this.x = postX;
    }
    if (0 + margin < postY && postY < 480 - margin) {
      this.y = postY;
    }
    this.mesh.position.x = this.x;
    this.mesh.position.y = Util.normalize(this.y);
  }

3D化

折角なので、見た目を豪華にするため、表示を3Dに変更しました。

Web上で3D表示を行なう際は、必ずと言っていいほどThree.jsが採用されます。今回も、Three.jsを使います。

three.js - Javascript 3D library

3Dとはいえ、当たり判定や移動ロジックなどは2Dのものをそのまま流用出来ます。2Dと同じ座標に3Dの物体を配置するようにするだけです。Java版は描画を各オブジェクトに任せるという抽象化を行っていたため、描画部分のみの変更で対応できました。

移植しての感想

  • 元バージョンの設計がある程度抽象化されていたため、それほど苦労せずに移植出来ました。
  • やはり3Dならではの描画の難しさがありました。ライティングや表示サイズを確認しながら作れないとかなり厳しいです。3DゲームでUnity等のオーサリングツールが主流になっている理由がよく分かります。
  • 3Dモデリング能力があればもっと見た目の良いものが出来たのでは無いかと思います。
  • Three.jsにはオンラインエディタが存在し、シーンをインタラクティブに作成することが出来ます。そちらを使うようにしたほうが良かったかもしれません。
  • 元バージョンではFlyweightパターンを意図したオブジェクトの使い回しを行っていましたが、今となっては不要なテクニックかもしれません。

オンラインエディタは下記のようなインタラクティブなものです。スクリプトも書けるため、使いこなすことで製作効率は上がりそうです。

f:id:iplcojp:20180305105507p:plain

ソースコード

ソースコードは下記からダウンロード出来ます。

https://iplanning.heteml.net/2018-shooting/ipl-shooting-src.zip

終わりに

JavaからWebアプリへ移行するに当たって、TypeScriptはおすすめの選択肢です。 弊社では業務アプリケーションのWebアプリへの移植も多く行っております。ご相談は弊社メールフォームよりお問い合わせ下さい。

*1:CordovaやElectronなどを使うことで、ネイティブアプリケーションのようなものを作ることが出来ます

*2:C#と言語デザイナーが同じという経緯があります。実際にはTypeScriptはJavaScriptの拡張として作られているので、あくまでスタイルが似ているということです

*3:JavaScriptはプロトタイプベースの言語であり、JavaScriptやTypeScriptのクラスシンタックスはプロトタイプのシンタックスシュガーです。Javaと同じ動作はしません。詳しくはJavaScriptのプロトタイプについて調べてみて下さい

*4:これ自体はVSCodeの機能というわけではなく、TypeScriptが備えているLanguage Serverという機能に対応しているエディタであれば利用可能です。例えばVimなどでも補完を使うことが出来ます

*5:JavaからJavaScriptへのコンバータは昔から存在します。ただしJavaAPIを実行するためのランタイムを抱え込む、JavaScriptのモダンなツールを使うことが出来ない、などのデメリットもあり、普段はコンバートよりもフルスクラッチに近い移植を選択しています

*6:勿論最終的にはanyがなくなることが望ましいです。anyを使うと型チェックや補完機能を活かすことが出来ません

GitLab CI を導入しました

f:id:iplcojp:20180223203025p:plain

GitLab で CI(継続的インテグレーション) をできるようにしました。

経緯

弊社では数年前から開発の助けとなるよう GitLab CE を運用しています。

社内で GitHub 同様にプロジェクト管理ができ開発作業に貢献してきました。 一方で、アプリの検証や納品といった作業のほとんどは手作業で行っていました。

手作業というのはとても温かみのある行為ですが、 同時にヒューマンエラーが入り込む温床となります。

また、それらの時間が削減できれば、その時間を開発に割り当てることができます。

そこで、検証や納品といった作業を自動化すべく GitLab で実現できる CI(継続的インテグレーション)サービス「GitLab CI」を導入してみました。

注意

本記事で示す GitLab CI の導入方法は、 公式ドキュメント をなぞったものです。 公式ドキュメントが性に合っているという方は、そちらをご覧下さい。

また、内容は GitLab が利用できることを前提としています。

GitLab CI の導入

GitLab CI を導入には、 次の2つの手順を踏みました。

  1. .gitlab-ci.ymlリポジトリのルートディレクトリに追加
  2. GitLab runner(以降、「runner」と呼ぶ)を設定

1. .gitlab-ci.yml をリポジトリのルートディレクトリに追加

CI を実行するということは、CIでやってほしいことがあるはずです。

そのやってほしいことを記述するファイルが .gitlab-ci.ymlです。 これを作成してリポジトリのルートディレクトリに追加します。

今回対象としたリポジトリpython なので、次のような .gitlab-ci.yml を追加しました:

 pytest:
   script:
     - python setup.py test

これは次のコマンドを実行することを意味します。

> python setup.py test

.gitlab-ci.yml の書き方は以下が参考になります:

2. GitLab runner を設定

さて、.gitlab-ci.yml にやってほしいことを記述したら、いよいよ実行させてみましょう。

CI を実行するのは、 GitLab runner と呼ばれる CI 実行ファイルです。 この runner を CI の実行環境(マシン。開発機と同じでも良い)で動作させることが最終的な目標となります。

runner は CI実行時に Git を使いますので、CI実行環境には以下を配置します:

  • GitLab runner
  • Git
  • その他、CIに必要な依存ツールがあれば

runner のダウンロードはインストールページ(Install GitLab Runner - GitLab Documentation)から行います。 当然ですが、環境に合致するものをダウンロードしましょう。

runner は、実行時にとあるユーザーとして Git 、必要ならその他依存ツールを使います。 そのため、ダウンロードした runner は、例えば、C:\GitLab-Runner\gitlab-runner.exe というように、管理者権限やユーザー権限に制限されない場所に配置します。

配置したら、runner の初期設定をします。

管理者権限で コマンドプロンプト(黒い画面)を起動し、次のコマンドを実行します。

> gitlab-runner.exe register

コマンドを実行すると、runner の設定に関する以下の7項目を入力することになります:

No 項目 内容
1. the gitlab-ci coordinator URL GitLab-CI を実行するリポジトリのURLを入力します。
2. the gitlab-ci token GitLab-CI を実行するリポジトリトークンを入力します。
3. the gitlab-ci description runner の識別名を入力します。
4. the gitlab-ci tags runner のタグを指定します。
5. Whether to run untagged jobs: タグ指定がないCIを実行をするか。
6. Whether to lock Runner to current project 現在のプロジェクトのみで使用するか。
7. the executor 何で実行するか。
  1. リポジトリページの Settings > CI / CD > Runners settings に記載されている URL を入力します。
  2. URLと同じ場所にに記載されている トークン を入力します。
  3. あなたの runner の名前を決めます。既定値はPC名になっています。
  4. CIの実行条件となるタグを入力します。これは後からでも変更できます。
  5. false が推奨です。限られたリポジトリのみで動作させるならば、使い勝手の点から true にしても良いです。
  6. true が推奨です。複数のリポジトリに対して使用したい場合は、false にしてもよいです。
  7. 特に理由がなければ shell にしましょう。dockerなどの環境が使える場合はそれらも選択肢となるでしょう。

煩雑な準備は以上でおしまいです。 そのままコマンドプロンプトで以下を実行すると、runner が利用できるようになります。

> gitlab-runner install
> gitlab-runner start

最後に

今回は特定のリポジトリに対する導入方法を紹介しました。

GitLab CI を導入したことで、疲れていたり眠かったりするときも 作業ミスが減り開発作業が捗るようになりました。

CI って難しいイメージがまだまだありますが、 やってみるとそれに見合うだけの結果は得られますので、 ぜひ皆さんもやってみてください。

以上です!

会社ホームページをjekyllからhugoに移行する

アイプランニングのホームページのビルド環境を、JekyllからHugoに置き換えました。

経緯

Jekyllは、非常に人気のある静的サイト生成ツールです。 当ホームページも、Jekyllで生成していました。

ただし、アイプランニングのホームページのような、非常にシンプルなサイトでも ビルドに若干時間がかかってしまう(手元ではフルビルドで6秒以上)という問題がありました。

また、社内はWindowsユーザが大半で、Rubyを使うにあたって敷居が高いという問題もありました。 もう少しお手軽なものに乗り換えようと考え、下記の静的サイトジェネレータを検討しました。

検討した静的サイトジェネレータ

f:id:iplcojp:20180223194741p:plain

Metalsmith

  • 機能を高度にモジュール化した静的サイトジェネレータです。
  • モジュールを組み合わせることでかなり細かく動作をカスタマイズできます。
  • Node.jsで動作しますが、Jekyllに比べて特に高速というわけではないようです。
  • 幾つかのプラグインがメンテされていないようでした。
  • エラーがあったファイル名を表示しないケースがありました。

f:id:iplcojp:20180223194845p:plain

Hexo

  • Node.js製のサイトジェネレータの中でも最も人気のあるツールで、かなり多機能です。
  • 今回はブログ関連の機能は不要なので、見送りました。

f:id:iplcojp:20180223194917p:plain

Hugo

  • Go言語で書かれており、圧倒的にビルドが高速です。
  • マルチプラットフォームのバイナリが配布されており、ランタイムが必要ないためインストールが簡単です。Macの場合はhomebrew経由でインストールできます。

ということで、今回はhugoを選択しました。

jekyllテンプレートからの移行

基本的に、hugoのディレクトリ規約に寄せていきます。

ディレクトリ構成は下記のようになりました。

.
├── config.toml
├── content(ここにmarkdownを配置)
├── layouts
│   ├── _default(ここにテンプレートを配置)
│   └── partials(HTMLの断片を配置)
├── package.json
├── public(生成先)
└── static(CSS, JSなどのアセットはここに配置)
  • まず、ビルド対象のコンテンツはcontent/に移動
  • staticファイルはstatic/に移動
  • layoutsの移行

  • 多段になっているlayoutを1段にする

    • 多段layoutに対応しているフレームワークは少ないので、多段構成から脱却しておくほうが選択肢が広がります。
    • なるべくpartialsに切り出して、繰り返しがあったとしても最小になるようにします。
  • sassを除去(弊社HP構築ではsassがそれほど有用ではなかったのと、ruby依存を切るため)

config.tomlを書く

config.tomlを下記のようにしました。

baseURL = "https://www.ipl.co.jp/"
languageCode = "ja-jp"
title = "株式会社アイプランニング〜プログラム開発請負"
uglyURLs = true

uglyURLsはデフォルトでfalseですが、これがfalseになっていると/content/test.html/content/test/index.htmlになってしまいます。 今回は移行目的だったため、リンクのメンテナンスを避けるためにuglyURLstrueにしました。

テンプレートの違いについて

hugoのテンプレートとして、layouts/_defaultにsingle.htmlを配置しました。

<!DOCTYPE html>
<html lang="ja">

{{ partial "header.html" . }}

<body>
    <div id="page">
        {{ partial "nav.html" . }}

        <div id="content" class="clearfix">

            <div class="top__animation">
                <a href="/recruit/">
                    <img src="/images/ipl_top_960.jpg" alt="2019年度新卒募集 プログラミング未経験者歓迎・人物重視 あなたの個性を活かしてみませんか?">
                    <canvas width="960" height="555"></canvas>
                </a>
            </div>

            {{ partial "sidebar.html" . }}
            <article>
                {{ .Content }}
            </article>
        </div>
        {{ partial "footer.html" . }} {{ partial "ga.html" . }}
    </div>
    <script src="/lib/jquery-2.1.4.min.js"></script>
    <script src="/index.js"></script>
</body>

</html>

基本的にはmustacheです。jekyllと似ていますが、違う点は、.Contentなどのプロパティ名がCamelCaseであることです。

詰まったところ

  • CamelCase.htmlが404になる

本件は理由が不明ですが、ひとまずkebab-case.htmlにリネームしました。

  • hugo server -wでファイル書き換えを検知してリビルドしてくれるサーバが起動するが、ファイル名変更は検知されない

ファイル名の変更や追加をした際、hugo serverを再実行する必要があります。

結果

jekyll3で6秒以上かかっていたフルビルドが、600ms〜700msに短縮されました。

インクリメンタルビルドではjekyll3も速い(2秒前後)ですが、それでもフルビルドのhugoの方が高速です。 快適にプレビューすることが出来るので、頻繁に更新するサイトに良さそうです。 また、副産物として、rssが自動生成されるようになりました。

問題点として、hugoのドキュメントがやや目的の情報を探しづらいと感じました。 オプションや暗黙の挙動などを調べる手間を考えると、基本的には提供されているサンプルから逸脱しない範囲でサイトを構成するのが良さそうです。

レゴによるスクラム体験

f:id:iplcojp:20171116171901j:plain

2018年卒の学生に向けて、一日インターンを実施しました。

業務内容の説明や業界説明、先輩社員との交流など、盛りだくさんの内容でしたが、 今回は業務体験の一環として実施した、「レゴによるスクラム体験」コーナーの内容をご紹介します。

概要

今回のインターンは、一日という短期間ですし、 プログラム未経験の学生も対象としていたので、 実際にプログラム開発の体験を行ってもらうのは、時間的に無理でした。

その為、「LEGO4SCRUM」というトレーニング手法を実施することにしました。

LEGO4SCRUMとは

LEGO4SCRUMは、「業務経験者」に向けた、スクラムのトレーニング手法です。

実施方法はWebページで公開されており、誰でも自由に見ることができます。

スクラムとは「開発チームが一体となって働くこと」を目的とした、アジャイルソフトウェア開発手法の1つです。開発メンバーが自発的にコラボレーションを行いながら、流動的な要求仕様の変化に対応し易い点が利点です。

LEGO4SCRUMとの違い

ただし、今回はあくまで「業務理解」のために行いたかったため、一部をアレンジして実施しました。

まず、一番良くあるケースである「システム開発の受託案件」をシミュレーションすること。スクラムの概念理解は「ストーリーポイントやベロシティの理解のみとする」こと、実際のスクラムは、スクラムリーダーがいるなど、方針が異なる点があります。

進め方

基本的には、LEGO4SCRUMで配布しているPDFをベースに、やり方を軽量化しました。実施時間はおおよそ1時間半〜2時間程度に収まります。

スライド

実際に使っているスライドを公開します。

https://iplanning.heteml.net/intern/ipl-seminar-r5-lego.pdf

ツール

IPLオリジナルのタイマーツールを作成しました。

レゴスクラムツール

準備

f:id:iplcojp:20171116171842j:plain

イントロダクション

  • 共同作業を促進するために、参加者に軽く自己紹介してもらいます。
  • LEGOを通じて、業務が体験できることを説明します。
    • プログラマーの仕事は、ただ席に座ってプログラムをするだけではなく、ヒアリングやチームでの共同作業など、コミュニケーションが多いことを説明します。

イデア出し

  • A3用紙を広げ、川や木、山などの地形図を自由に書きます。
  • 「理想の街」にはどんな建物があるか、自由に発想してもらいます。ダメ出しはしません。
  • イデアは付箋上に自分で書いてもらいます。書いてもらった付箋はホワイトボードに貼りつけます。
  • だいたい参加者2名の場合で10件くらいアイデアができればOKです。

ルールの説明

f:id:iplcojp:20171116171626p:plain

  • 計画し、協力することが大事というメッセージを伝えます。
  • 作成作業に「7分」の時間制限があることを説明します。

見積もり

  • ストーリーポイントの説明を行ないます。
  • スイムレーンウォールをホワイトボードに書きます。
  • ストーリーポイントに従って振り分けを行ないます。

ストーリーポイントとは、作業量を表す架空の単位です。

例えば、一階建ての家を「ストーリーポイント1」とすると、二階建ての家は二倍の労力がかかると見込まれるため、「ストーリーポイント2」と置きます。

所要時間のような、絶対的な単位で作業を見積もることは熟練者でも非常に難しいですが、 相対的に見積もるのは比較的簡単なので、このような手法を用います。

スイムレーンウォールとは、下記画像のようなものです。

f:id:iplcojp:20171116171649p:plain

付箋に書いたタスクを一つづつ取り出し、それぞれどれくらいのストーリーポイントを割り当てるべきか、 参加者に聞き、該当するストーリーポイントのレーンに貼っていきます。

スイムレーンに分配し終わったら、それぞれのカードにストーリーポイントを書いていきます。

ストーリーポイントの見積もりには「プランニングポーカー」というカードを使った見積もり手法が有名ですが、 スイムレーンウォールはより高速な見積もり手法です。 今回は時間の制約があるため、スイムレーンウォールを採用しました。

スタート

f:id:iplcojp:20171116171703j:plain

  • 付箋を「バックログ」に移動し、スプリントの箱をホワイトボードに書きます。
  • ストップウォッチをホワイトボードに映します。

【1】スプリント計画(3分)

  • バックログから「7分でやれる量」を取り出し、スプリントに移動してもらう

【2】スプリント(7分)

  • 実作業
  • 4分経過時に声をかける

【3】スプリントレビュー(5分)

  • 改善案を伝え、付箋に書いてもらう
  • 改善案について追加見積もりを行なう
  • チームのベロシティを伝え、次の計画に活かしてほしいと伝える
  • 未完成のものがある場合は、それを含めない値をActualの参考値として出しておく

これを、3回繰り返します(1回につき15分かかります)

まとめ

  • 参加者に、次回同じことをするとすれば、どうしたら更に良くなるか、方法を考えてもらいます。
  • 実務に則ったものであることを説明します。実施者が顧客と実際にあったやり取りなどを例に出して、どうしてきたかを伝えます。

実施風景

f:id:iplcojp:20171116171716j:plain 小さなパーツを見つけるのが大変なようで、あとで小さいパーツは袋に分ける工夫を行いました。

f:id:iplcojp:20171116171725j:plain

チームによって、装飾のセンスが出てくるので見ていて面白いです。

f:id:iplcojp:20171116171737j:plain

オリジナル版では4人以上を推奨としていますが、初対面のメンバーだと案外2〜3名程度の方が、積極的に参加してくれるので感触としては良かったです。

f:id:iplcojp:20171116171747j:plain

「理想の街」の条件として、交通機関が挙げられることが多かったです。要望を出す側も交通機関があると生活動線からの意見を言ったりと、やりやすい面が有りました。

f:id:iplcojp:20171116171759j:plain

スプリントの枠は最初から3つ書かない方が良いようです。ベロシティが出る前に、全体の計画に意識が行ってしまいます。

f:id:iplcojp:20171116171810j:plain

f:id:iplcojp:20171116171820j:plain

実施者が市長になりきって、「市長の家」や「市長の像」をリクエストするとやりやすいですね。「市長の像」は特に無茶な要求を思いつきやすいという事情もあったりします(作る側は大変そうでしたが)。

参加者から寄せられた感想

参加者からは、以下のような感想が寄せられました。

  • プロジェクトをチームで進めていく際のプロセスがわかりやすく体験出来た。
  • 大きなものの見積もりが難しいと感じた。
  • システムエンジニアの仕事内容が、イメージとズレがあったが、早めにズレをなくせてよかった。
  • 実際にお客様と話し合って欲しいものを探していく仕事内容が面白そうでとても興味が湧いた。
  • 最初はこれで分かるのか疑問に思っていたが、やり終わった後にだいたいのイメージが湧いて、少し理解できた。
  • 1回目はかなり時間が余ってしまい、見通しが甘かったと感じた。2回目では経験を活かし、ちょうどいい割当が出来た。3回目では作業を一つ忘れてしまい、目標を達成することができなかった。
  • 次に同じことを行なうとしたら、作業一つ一つに責任者を付け、チーム・要求者でどのようなものを作るのかという意識を統一して行いたい。
  • チームでの開発の大変なところや忘れがちなポイントも、レビューの結果を交えて説明されていたことが、とても参考になった。
  • お客様からの要求をしっかりと確認しておくことで、余計な工程を減らすことが出来るということがわかり、確認の繰り返しも重要な仕事だと感じた。

実施者としての感想

システム開発のプロセス体験としても十分に使える、かなりよく練られた手法であると感じました。また、LEGOを使ったことで、かなり敷居の高さを抑えることが出来ました。

実際に起きるコミュニケーションもなかなかリアルで、例えば1回目のイテレーションで「おもったよりこなせてしまった」チームが、2回目で欲張った計画を立ててしまい、炎上して計画の見直しを余儀なくされるシーンがありました。 慌てる参加者とは裏腹に、実施者としては「この失敗をしてほしかった!」と思っていました。

一方、チームによって作る建造物のスケール感がバラバラで、最初に小さく作りすぎてしまい、最初に切ったチケットの大半が2イテレーションで消化されてしまうことがありました。最初にスケール感を指定して置くことが大事かもしれません。