Ionic v3自作プラグインの基礎

Ionic Advent Calendar 2018 の17日目の記事になります。

Ionic界隈ではv4だのCapacitorだので盛り上がっていますが、最近書く機会があったので備忘録程度にv3にて自作プラグイン(Cordova)を追加する方法を書きたいと思います。

プロジェクトの作成

はじめにプロジェクトを作成します。

$ ionic start myApp tabs
$ cd myApp

Cordovaプラグインの作成

次に plugman を使ってCordovaプラグインの雛形を作ります。
入っていない場合はインストールしてください。

$ npm install -g plugman

自作プラグインの作業場所として適当に plugins_src ディレクトリを作ります。
ディレクトリに入り plugman create コマンドを使ってHogeプラグインを作成します。
その後 plugman platform add コマンドにてプラットフォームを追加します。
今回はAndroidプラグインを作りますが、iOSの場合は適宜書き換えてください。

$ mkdir plugins_src
$ plugman create --name Hoge --plugin_id cordova-plugin-hoge --plugin_version 0.0.0 --path plugins_src
$ cd plugins_src/Hoge
$ plugman platform add --platform_name android

結果、下記のような構成が出来ます。

  • plugin.xml プラグインの設定
  • src/android/Hoge.java 実際のAndroidのコードを記述する
  • www/Hoge.js Webviewとのインターフェース
plugins_src/Hoge/
├── plugin.xml
├── src
│   └── android
│       └── Hoge.java
└── www
    └── Hoge.js

中身はこんな感じです。coolMethod、なんてクールな名前なんだ。
やってることは渡ってきた文字列をそのまま返しているだけですね。

<?xml version='1.0' encoding='utf-8'?>
<plugin id="cordova-plugin-hoge" version="0.0.0" xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android">
    <name>Hoge</name>
    <js-module name="Hoge" src="www/Hoge.js">
        <clobbers target="cordova.plugins.Hoge" />
    </js-module>
    <platform name="android">
        <config-file parent="/*" target="res/xml/config.xml">
            <feature name="Hoge">
                <param name="android-package" value="cordova-plugin-hoge.Hoge" />
            </feature>
        </config-file>
        <config-file parent="/*" target="AndroidManifest.xml">
        </config-file>
        <source-file src="src/android/Hoge.java" target-dir="src/cordova-plugin-hoge/Hoge" />
    </platform>
</plugin>
var exec = require('cordova/exec');

exports.coolMethod = function (arg0, success, error) {
    exec(success, error, 'Hoge', 'coolMethod', [arg0]);
};
package cordova-plugin-hoge;

import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CallbackContext;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

/**
 * This class echoes a string called from JavaScript.
 */
public class Hoge extends CordovaPlugin {

    @Override
    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
        if (action.equals("coolMethod")) {
            String message = args.getString(0);
            this.coolMethod(message, callbackContext);
            return true;
        }
        return false;
    }

    private void coolMethod(String message, CallbackContext callbackContext) {
        if (message != null && message.length() > 0) {
            callbackContext.success(message);
        } else {
            callbackContext.error("Expected one non-empty string argument.");
        }
    }
}

今回はこれをちょっと編集してメソッドの実行時に一瞬バイブするよう書き換えたいと思います。
ついでにパッケージ名を変更。

     <platform name="android">
         <config-file parent="/*" target="res/xml/config.xml">
             <feature name="Hoge">
-                <param name="android-package" value="cordova-plugin-hoge.Hoge" />
+                <param name="android-package" value="com.example.hoge.Hoge" />
             </feature>
         </config-file>
         <config-file parent="/*" target="AndroidManifest.xml">
+            <uses-permission android:name="android.permission.VIBRATE" />
         </config-file>
         <source-file src="src/android/Hoge.java" target-dir="src/cordova-plugin-hoge/Hoge" />
     </platform>
-package cordova-plugin-hoge;
+package com.example.hoge;

 import org.apache.cordova.CordovaPlugin;
 import org.apache.cordova.CallbackContext;
@@ -7,6 +7,9 @@ import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;

+import android.content.Context;
+import android.os.Vibrator;
+
 /**
  * This class echoes a string called from JavaScript.
  */
@@ -24,6 +27,7 @@ public class Hoge extends CordovaPlugin {

     private void coolMethod(String message, CallbackContext callbackContext) {
         if (message != null && message.length() > 0) {
+            ((Vibrator) cordova.getActivity().getSystemService(Context.VIBRATOR_SERVICE)).vibrate(100);
             callbackContext.success(message);
         } else {
             callbackContext.error("Expected one non-empty string argument.");

最後にプラグインのインストール時に必要となるpackage.jsonを下記のコマンドで作成します。

$ plugman createpackagejson .

CordovaプラグインのIonic Native化

これでCordovaプラグインは完成しましたがIonicで使うためにIonic Native化(Typescript化)します。
任意の場所に ionic-team/ionic-native を置きます。
今回は例として本家を直接サブモジュール化していますが実際使うならフォークしたものを使用してください。
gulp plugin:create -n [プラグイン名]で雛形を作成します。

$ cd ../..
$ git submodule add https://github.com/ionic-team/ionic-native plugins_src/ionic-native
$ cd plugins_src/ionic-native/
$ npm install
$ gulp plugin:create -n Hoge

src/@ionic-native/plugins/hoge/index.tsが出来上がるので下記の様に編集します。
import文はtslintさんが怒ってくるので。

  *
  */
 import { Injectable } from '@angular/core';
-import { Plugin, Cordova, CordovaProperty, CordovaInstance, InstanceProperty, IonicNativePlugin } from '@ionic-native/core';
+import { Cordova, CordovaInstance, CordovaProperty, InstanceProperty, IonicNativePlugin, Plugin } from '@ionic-native/core';
 import { Observable } from 'rxjs/Observable';

 /**
@@ -36,24 +36,23 @@ import { Observable } from 'rxjs/Observable';
  */
 @Plugin({
   pluginName: 'Hoge',
-  plugin: '', // npm package name, example: cordova-plugin-camera
-  pluginRef: '', // the variable reference to call the plugin, example: navigator.geolocation
+  plugin: 'cordova-plugin-hoge', // npm package name, example: cordova-plugin-camera
+  pluginRef: 'cordova.plugins.Hoge', // the variable reference to call the plugin, example: navigator.geolocation
   repo: '', // the github repository URL for the plugin
   install: '', // OPTIONAL install command, in case the plugin requires variables
   installVariables: [], // OPTIONAL the plugin requires variables
-  platforms: [] // Array of platforms supported, example: ['Android', 'iOS']
+  platforms: ['Android'] // Array of platforms supported, example: ['Android', 'iOS']
 })
 @Injectable()
 export class Hoge extends IonicNativePlugin {

   /**
    * This function does something
-   * @param arg1 {string} Some param to configure something
-   * @param arg2 {number} Another param to configure something
+   * @param message {string} Some param to configure something
    * @return {Promise<any>} Returns a promise that resolves when something happens
    */
   @Cordova()
-  functionName(arg1: string, arg2: number): Promise<any> {
+  coolMethod(message: string): Promise<any> {
     return; // We add return; here to avoid any IDE / Compiler errors
   }

ビルドします。
成果物は dist/@ionic-native/hoge に置かれます。
これで準備は整ったのでプロジェクト直下に戻ります。

$ npm run build hoge
$ cd ../..

Ionicで実装

最後に実際に使うIonic側のコードを記述します。
src/app/app.module.ts に登録した後、 src/pages/home/home.ts にメソッドを追加してボタンで呼ぶようにします。

 import { StatusBar } from '@ionic-native/status-bar';
 import { SplashScreen } from '@ionic-native/splash-screen';
+import { Hoge } from '@ionic-native/hoge';

 @NgModule({
   declarations: [
@@ -34,6 +35,7 @@ import { SplashScreen } from '@ionic-native/splash-screen';
   providers: [
     StatusBar,
     SplashScreen,
+    Hoge,
     {provide: ErrorHandler, useClass: IonicErrorHandler}
   ]
 })
 import { Component } from '@angular/core';
 import { NavController } from 'ionic-angular';
+import { Hoge } from '@ionic-native/hoge';

 @Component({
   selector: 'page-home',
@@ -7,8 +8,14 @@ import { NavController } from 'ionic-angular';
 })
 export class HomePage {

-  constructor(public navCtrl: NavController) {
+  constructor(public navCtrl: NavController, public hoge: Hoge) {

   }

+  coolMethod() {
+    this.hoge.coolMethod('aaaa').then(message => {
+      alert(message);
+    });
+  }
+
     Take a look at the <code>src/pages/</code> directory to add or change tabs,
     update any existing page or create new pages.
   </p>
+  <button ion-button (click)="coolMethod()">coolMethod</button>
 </ion-content>

プラグインをインスールして実機で確認。

$ ionic cordova plugin add plugins_src/Hoge
$ npm install plugins_src/ionic-native/dist/@ionic-native/hoge
$ ionic cordova run android --device

COOLMETHODボタンを押すたびに端末がブルブルと震えるようになりました。
やったね。

f:id:saihoooooooo:20181214164955j:plain:w300

まとめ

初歩の初歩ですが割と簡単にできたと思います。
Capacitorだともっと楽にネイティブの機能を呼び出せるっぽいので期待ですね。

ゼルダの伝説BotWのRTAが俺を惹きつけてやまない

※この記事はスプラトゥーンの話題を含みません。
※この記事はゼルダの伝説BotWのネタバレを含みます。

Splathon Advent Calendar 2018 の11日目の記事になります。

まえがき

ゼルダの伝説BotW面白いですね。
「記憶をなくしてもっかいやりたい」なんて声がちらほら聞こえてくる正真正銘のザ・神ゲーです。
僕も発売当時はSplathonの#zeldaチャンネル、#zelda_netabareチャンネルに入り浸り、そしてハイラルに入り浸り、やがて現実との区別がつかなくなり、仕事中にもかかわらず「早く現実(ハイラル)に戻りたい...早く...」と呟きながらポロポロと涙をこぼしていました。

RTAとAny%について

そんなゼルダの伝説BotWの面白さのひとつがとにかく「自由」であるということ。
自由すぎてチュートリアル終了後はストーリーを全無視していきなりラスボスに挑戦することも可能だったりします。
ツール等を使わずに人力でのゲームクリアまでのタイムを競うことをRTA(Real Time Attack)と呼びますが、ゼルダの伝説BotWはとってもRTA向きなゲームだと言えるでしょう。

また一口にRTAと言っても複数のレギュレーションが存在しており、その中でも今回は最も一般的であろうAny%というレギュレーションに注目したいと思います。
これは要するにダンジョンやイベントの進行度、そのルート等を一切考慮しない純粋なクリア時間を競うもので、ゼルダの伝説BotWではオープニングムービーが終わってリンクを操作できるようになった瞬間から魔獣ガノンに最後の光の矢が当たるまでの時間を計測します。

Any%の世界記録

現在の記録は果たしてどれほどなのか、タイムアタックの記録集積サイトのspeedrun.comで確認してみましょう。

※2018/12/11現在。

f:id:saihoooooooo:20181211004358p:plain

f:id:saihoooooooo:20181205133417p:plain

なんと驚異の31分06秒890...!
いくらなんでも速すぎだし、自分だったらまだイノシシを狩れているかどうかも怪しいです。
RTA対象のゲームによっては「バグを利用して強制的にエンディング見る」といった内容のものもありますが、ゼルダの伝説BotWにおいて今のところそういった類のバグは見つかっていません。
キチンと正攻法でガノンを討伐した上でこのスピードとなっています。

一体どうやったらそんなに速くクリア出来るのか。
実際のRTA動画は以下になります、是非その目で確かめてみてください。

Wolhaiksong19の Any% no amiibo in 31:06.870をwww.twitch.tvから視聴する

ルート&テクニック解説

如何でしたでしょうか。
内容が凄すぎて「俺の知ってるゼルダと違う!」と思われたかもしれません。
ここからは簡単ではありますが攻略のルートとそこで使われてるテクニックを説明したいと思います。
また主なテクニックは開発者が意図していない不具合を利用したバグ技がほとんどでRTA界隈ではグリッチと呼ばれています。

事前準備

戦いはゲーム開始前から始まっています。
Any%のレギュレーション上、道中のムービーやロード時間も計測されてしまうためなるべく削減できる環境を選択する必要があります。
現状では「WiiU版でドイツ語」一択だそうです。
WiiU版の方が本体性能では劣るものの描画内容が少なく、ドイツ生まれのゼルダは短い言葉で簡潔に内容を伝えてくれます。

回生の祠〜

準備が整ったところで本編スタートです。
ゼルダの伝説BotWの象徴的なシーンの一つ、リンクが回生の祠を抜けた瞬間にムービーが始まって広大な大地を映した時にこの世界への期待が胸を膨らましたことをよく覚えています。
しかし前述の通り感動的なムービーなど完全に無意味な為、望遠鏡壁抜け(Scope Clip)なる技を使い祠の側面から出ることで発生を防ぎます。
望遠鏡を使うとわずかにリンクの位置が後ろにずれる挙動を利用しています。

壁抜けの後は始まりの塔の起動イベントすらも無視してワ・モダイの祠に向かいます。
防寒着が必要になる地帯を裸で強引に突破していくのは見ていて非常にハラハラします。
道中では盾サーフィンバグで利用するナベのフタ、ビタロックジャンプに必要な斧・弓矢を拾って(奪って)おきます。

全体を通して移動については口笛ダッシュ(Whistle Sprinting)と呼ばれるグリッチを使用しています。
口笛を吹きながらBボタンを連打することでがんばりゲージを消費せずにダッシュすることができます。
通常のダッシュの方が僅かに速いらしいので状況に応じて使い分けられると良さそうです。


ワ・モダイの祠(ビタロック)〜

祠につきましたが始まりの塔を起動していないので通常の方法では中に入ることができません。
ここでは盾サーフィンバグ(Shield Skew Clip)を使い壁抜けをします。
傾斜のある地面に向かって盾サーフィンするとその状態(Skew)が内部で保存され、再度別の場所で盾サーフィンした時に位置ズレを発生させることが出来るようです。

※Shield Skewは原理が複雑で奥が深いのですがこちらのリンクが詳しいです。
ch.nicovideo.jp

祠の中では鉄のハンマーを拾うのを忘れずに。
ゴール前でさらっと盾サーフィン2段ジャンプ(Shield Jump)をしているのがかっこいいです。

攻略後、次の祠までの移動はビタロックジャンプ(Stasis Launch)を使います。
自分で打ち出した物体に飛び乗って移動するというタオパイパイ的なアレです。
岩や切り倒した木にビタロックを使い攻撃でダメージを溜めてから最後に弓矢で方向を調整するのがコツです。
両手武器だと楽にダメージを溜めることができるので先程拾ったハンマーや斧を活用します。
矢は貴重なので木に刺さったものを回収するのも忘れずに。


トゥミ・ンケの祠(アイスメーカー)〜

祠の中で重要になるのはアイスメーカーで扉を持ち上げた後の2階へのショートカットでしょうか。
アイスメーカーと盾サーフィンジャンプを併用して高さを稼ぐことができます(Cryonis Jump)。

祠クリア後、次のマ・オーヌの祠までは結構な距離があるのですがここで大技が炸裂します。
通称BtB(Bullet-time Bounce)と呼ばれているグリッチです。
盾サーフィン+空中で弓矢を構えた時のスローモーションの状態で敵に衝突することで超スピードで吹っ飛ぶという技です。
紹介しているグリッチの中では比較的最近発見されたものでこれにより大幅なルートの変更、そしてタイムの短縮に繋がったようです。

automaton-media.com

ただし位置取りや角度がとてもシビアで狙った方向に飛んでいくのは至難の技です。
RTA動画内では投擲モーション・盾でのはじき動作や斧の振りかぶりなど一見無駄な動作を入れていますが、この一連の動作を「セットアップ」と呼び安定して同じ結果が出るように細かく位置を調整しています。
最終的な立ち位置・飛び先が重要であり唯一解は無い為、RTAプレイヤーはそれぞれ独自のセットアップを持っています。

またBtBが成功したとしてもこの時点ではパラセールを持っておらず落下ダメージで即死してしまう為、落下ダメージキャンセル(Fall Damage Cancel)も合わせて使う必要があります。
空中で投擲モーションになりその最中に装備を外すことでダメージをキャンセルできるのですが、これまたタイミングがシビアで「BtB→落下ダメージキャンセル」はこのルートにおいて難関ポイントのひとつだと思われます。
実際同じプレイヤーが別の動画でミスしているのを何度か見ました。

RTA中に死んでしまったり取り返しのつかないミスをしてしまった場合はその時点で終了しオープニングからやり直しとなります。
ですので理論上は速いが高難易度、またはランダム要素が存在するルートは正に修羅の道と言えるでしょう。
今回の動画は成功した部分だけを切り取ったもので、その裏には数え切れない失敗や挫折が隠されています。

マ・オーヌの祠(マグネキャッチ)〜

祠内部では特筆すべきことはありません。
適切にマグネキャッチを使いサクッとクリアしましょう。

次がいよいよ最後の祠です。
ビタロックジャンプを2回使ってジャ・バシフの祠を目指します。
祠周りの水場に「これをマグネキャッチで拾ってビタロックジャンプしてくれ」と言わんばかりの丁度いい鉄板が落ちているので利用します。

ジャ・バシフの祠(リモコンバクダン)〜

リモコンバクダンはロスが無いように、またダメージを食らってしまわないように正確に設置・爆破します。
足元に置きたい場合はジャンプ中にリモコンバクダンを出すことでスムーズに設置できます。

クリア後はパラセールを受け取りに時の神殿跡に向かいます。
正面の壁を壊した先に居るガーディアンは初めての遭遇時にムービーが始まってしまうのですが、先に矢でダメージを与えて敵対モードにしておくことでキャンセル出来るようです。

一番近くに生えている木を使いビタロックジャンプで時の神殿跡の屋根の上まで飛んでいきます。
またこの木にはランダムでツルギカブトが止まっているのですが、ハイラル城到着までに捕まえておく必要があり実質これが最後のチャンスになります。
ツルギカブトを安定して取れる別のルートは存在するもののこちらの方が圧倒的に速いです。
もし居なかった場合、一応は救済の可能性があります。

時の神殿跡〜

ハイラル王からパラセールを受け取り、いよいよハイラル城に向かう準備が整いました。
ここでもBtBの出番がやってきます。
塔の真下に居るボコブリンを利用して一気にハイラル城まで到達することが出来ます。
がんばりゲージ的にギリギリなので足りなそうな時はパラセールを開閉して乗り切りましょう、パラセールを閉じてもBtBのスピードは維持されます。

ちなみにBtBと落下ダメージキャンセルを使って初手でハイラル城に向かえばいいんじゃないの?と思う方も居るかもしれませんが、「パラセールを取得する」という行動がフラグ管理されているようで、その前にはじまりの大地を脱出しようとするとリンクが「アアアァァァァーーーーーーッ!」と叫びながら奈落に落ちていきます。
既に色んな方法が試されているようで期待薄ではありますが、もしもチュートリアルすら無視できる方法が発見されれば大幅なタイムの短縮につながるかもしれません。

ハイラル城〜

いよいよ終盤です。
ここからは派手なアクションは少なくなりますが細かいテクニックや如何に効率よく武器を集めるかなどの知識が要求されます。
また敵と遭遇することも多いので気は抜けません。

木箱の部屋

左の木箱からは矢が、右の木箱からはランダムでフルーツが3,4個出ますがフルーツの箱からツルギバナナが出なければほぼ詰みです(後述)。
さらにツルギカブトが取れていない場合はツルギバナナが2個必要です。

食堂

ここでのミッションは以下になります。

  • シャンデリア上の王家の弓をマグネキャッチで拾う
  • 暖炉の近衛の槍をマグネキャッチで拾う
  • モリブリンから岩砕きを奪う
  • チカラ薬(Lv3)を作成する

敵に一定以上のダメージを与えることで持っている武器を落とすのですが、黒モリブリンを正面から殴っても時間がかかる上に武器の耐久値も消費してしまいます。
そこでリモコンバクダンを投げそちらに意識を集中させている間に背後からふいうち(ダメージ8倍)で2回攻撃します。

またふいうちをする場合、口笛ダッシュだと先に敵に見つかってしまう為ここでは投擲モーション・解除をしながらのダッシュ(Throwing Weapon Sprinting)でカチャカチャと移動します。
誤って武器を投げてしまう可能性があるので注意。

モリブリンへの攻撃の合間にはチカラ薬を作成しておきます。
Lv2とLv3ではボスの撃破時間が数十秒変わってくるのでここではLv3の作成が必須条件となります。
薬をLv3にするためには虫と魔物素材を入れた上で薬効値(素材ごとに異なる)が合計で300以上必要となり、はじまりの大地でのツルギカブト(40)と木箱からのツルギバナナ(90)が取れている場合は、食堂内に落ちているもう一つのツルギバナナ(90)とツルギダケ(90)で合わせて300を超えるので問題ありません、魔物素材は同じく食堂内のモルドラジークの背びれを使います。
ツルギカブトは取れなかったけど木箱からツルギバナナが2個出たという場合は食堂内のツルギバナナ・ツルギタケ・岩塩を合わせてチカラ塩焼きキノコLv3を作成することが出来ます(ただし効果時間は短い)。
どちらも達成出来ていないと調理大成功でのLvアップを祈るしかなくそれにも漏れてしまった場合はリセットとなります。
ここ最近のAny%の上位はBtBなどの派手なテクニックを見せつつ裏ではカブトムシ&バナナお祈りゲーになっているのかもしれません。

隠し部屋

リモコンバクダンで壁を壊して近衛の剣を拾います。

訓練所

先程拾った近衛の剣でモリブリンをふいうちして王家の両手剣を奪います。
あとは壁に立てかけられている近衛の弓、木箱内の大量の矢をGETします。
ここから図書室に向かう際にリザルフォスが2匹出ます、奥に居る方は出来るだけ反応しないように引きつけてから角に矢を当てて怯ませます。

図書室

リザルフォスから三叉リザルブーメランを奪った後、鉄箱でのビタロックジャンプで一気に本丸に突撃します。

ボス戦〜

ついにボス戦です。
チカラ薬を使ってガンガン攻撃していきましょう。
今更ですがこちらの装備は裸なのでほとんどの攻撃は一撃でも食らえばアウトです。

カースガノンx4

神獣を一体も開放していないのでここで全てのカースガノンと戦う必要があります。
死闘を繰り広げるというよりは基本的に弓矢で怯ませてから両手剣のタメ攻撃で削るだけですが。
風のカースガノンには近衛の槍を使い、雷のカースガノンは盾を構えたらリザルブーメランで弾きましょう。

厄災ガノン

同じく神獣未開放の為、体力MAX全力ガノンが相手です。

第一形態は初手+αでラッシュを決める(避ける)必要がありますがそれ以外は近づかずに矢で攻撃し続けます。
天井に登った場合はバクダン矢を撃つと落ちてきます。

第二形態はレーザーを反射できさえすればタメ攻撃のハメ攻撃で沈んでしまいます。
ハメられている姿があまりにも不憫で少し可哀想。

魔獣ガノン

ここまで来たら終了したも同然。
光の矢は直線上に飛んでいくのでエイムに困ることもないはずです。
以上で解説は終わりです、お疲れ様でした。

計測は終了しているので感動的なエンディングも見放題です、やったね。
ただしもちろんゼルダのことは覚えていません。

終わりに

長々と書いてしまいましたが最後までお付き合いいただきありがとうございました。

自分も最初は何をやっているのかわからずただ眺めていただけでしたが行動の意味がわかるにつれ更に楽しめるようになったと思います。
超人的なテクニックを用いて数多の挑戦の末に運要素をもくぐり抜けた奇跡、それこそがAny%の醍醐味なのかもしれません。
現在一位のWolhaiksongさんですら更に記録を塗り替えようと日々挑戦を続けておりその様子をLive配信しています。
興味のある方は是非ご覧になってください。

www.twitch.tv

ゼルダの伝説BotWのRTAは非常に活発でBtBのようなグリッチや新しいルートが日々発見・研究されています。
いずれ30分を切るような大記録も出てくるのではないかと思うと非常に楽しみでこれからも目が離せないコンテンツです。

ErgoDox買ったったった(そしてハウツーConfigurator)

巷で話題のセパレートキーボード、ErgoDox買っちゃった俺マジ異端児。
こんばんわ、saihooooooooです。
フリースタイルラップのTV番組を見ながら記事を書いてしまいました。

という訳でErgoDoxですが、ご存知でしょうか。
ErgoDox is 何?という方は下記サイトを御覧ください。
自分もこの記事に触発されて買ったクチです。

nippondanji.blogspot.jp

もともと自分も無駄にガタイが良いためか肩こりがひどく、エルゴノミクスキーボードというものを知ってからは同じくセパレート型キーボードであるKinesis Freestyleを愛用していました。
Kinesis Freestyleはとても良い製品で特に不満も無かったのですが、ErgoDoxの「オープンソースキーボード」というイケてる響きにミーハーな僕は飛びついてしまいました。

買う

本来3Dプリンタやハンダごてを駆使して作り上げるErgoDoxですが、工作経験のない自分が一から組み立てるのは無理なので組み立てまでを行ってくれるFalbaTech社(在ポーランド)から買うことにしました。

falbatech.pl

あとキーキャップが売り切れだったため(今見たら普通に売ってた、、)PIMP MY KEYBOARDというサイトから別途買いました。

pimpmykeyboard.com

上記から「ErgoDox Base set」と「ErgoDox Modifier set」をそれぞれ購入。
青が好きなので選ぼうと思ったら同じ青でも4種類あったので、この辺の色見本を参考に「Blue (BDJ)」を選びました。

糞マニアックなキーボードにも理解ある妻からのバレンタインデープレゼントという名目だったので2/14に注文後、ErgoDox本体は2/26に、キーキャップの方は2/29にそれぞれ到着。
わりかし早かったでしょうか。

そして届いたのがこちら。

f:id:saihoooooooo:20160302010319j:plain:w600

THE・雑!!!!!
とりあえず剥いてみる。

f:id:saihoooooooo:20160302010333j:plain:w600

それっぽい箱が出てきました!
若干凹んですが気にしません!

f:id:saihoooooooo:20160302010346j:plain:w600

そしてついにErgoDoxとの対面!
ウォオォォォォォォ、テンションMAXXXXXXXXX!!!!

f:id:saihoooooooo:20160302010431j:plain:w300

あとなぜかおまけのおやつが同梱されていました!
不味そう!!!

この勢いでキーキャップを付けていきます。
チェリー軸なので+に合うようにはめていくだけです。

f:id:saihoooooooo:20160302010405j:plain:w300

ホームポジションとなるFとJのキーは若干深め?に作られているようなので注意してください。
ちょっとわかりにくいですが、右がFとJ用のキーです。
そして、、。

f:id:saihoooooooo:20160302010415j:plain:w600

完成しました!!!!!
どうですかこの21世紀感。
やっぱり時代はオープンソースキーボードですね。
オープンソース最高。

設定する

さて、ここからはぼくがかんがえたさいきょうのキー設定を行っていきたいと思います。
ErgoDoxはキー設定を自由に入れ替えられることも大きな特徴です。

キー設定の手順は以下の通りです。

  • PCにTeensy Loaderをインストール
  • .hexファイルを生成
  • PCにErgoDoxを繋ぐ
  • Teensy LoaderにてErgoDoxに.hexファイルを読み込み&再起動

まずここからTeensy Loaderをインストールします。
ご自身の環境に合ったものを選んでください。
Teensy Loader Application - available for Windows, Linux and Macintosh systems

で.hexファイルっていうのはあんまり聞いたことないですが、どうやら何かのテキストファイルを何かのコンパイラに通すことで出来上がるようです。
よくわからないので自動作成してくれるサービスを利用しました。

keyboard-configurator.massdrop.com

共同購入サイトのMassdropのアカウントさえあれば、設定を保存したりロードしたり.hexファイルをダウンロードしたり色々できるようです。
win用、mac用なんかを別々に保存しておけば都度サクッと書き換えや更新ができるので便利度高めです。

f:id:saihoooooooo:20160302031230p:plain:w600

僕はとりあえずこんな感じで設定しました。
これからもちょいちょい自分の手に馴染むように更新していくと思います。

.hexファイルをダウンロードしたら、PCにErgoDoxを繋いでTeensy Loaderを起動します。

f:id:saihoooooooo:20160302032049p:plain

起動するとまず「Press Button To Activate」と表示されます。
本来ならここで物理ボタンを押す必要があるのですが、ErgoDoxはキー押下で同じ命令を出すことが出来るようです。
さきほど.hexファイルを生成したレイアウト上で「Teensy」となっているキーを押します。
初期設定ではレイヤー2の一番左上のキーになります。

ちなみに新しいキー配置でこのキーを押せるように設定しておかないと次回から物理ボタンを押さなければならなくなるので要注意です。
僕が注文したケースは穴が開いてるのでボールペンとかで押せそうですが、ケースによっては分解しないといけないかも、、。

f:id:saihoooooooo:20160302032139p:plain

これで準備が整いました。

f:id:saihoooooooo:20160302032116p:plain

つづいて上記のボタンで.hexファイルを開きます。

f:id:saihoooooooo:20160302032204p:plain

開いたら上記のボタンでErgoDoxに読み込み。

f:id:saihoooooooo:20160302032217p:plain

読み込みdone。

f:id:saihoooooooo:20160302032225p:plain

その後上記のボタンで再起動を行います。

f:id:saihoooooooo:20160302032234p:plain

これで無事キー設定が完了しました!
お疲れ様でした。
思う存分キーを叩いてください。

まとめ

まだ使い始めて1日そこらなので不慣れなことも多いですが、とにかくすごく捗りそうです。
キーボードによる肩こりの激しい方、自由なキー配置を求めてやまない方は幸せになること間違いなしです。
ErgoDoxマジでヤバイYO!(サーセン

スプラトゥーンのブキ抽選ツール「イカすロット」を作った with Meteor

全国100万人のスプラトゥーンガチ勢の皆さん、イカがお過ごしでしょうか。

僕はここ最近プラベ・タッグをよくやっています。
特にプラベ、いいですよね。
VCしながらワイワイ、勝っても負けても楽しくって、味方ガー!って胃に穴が空くこともない(戦犯は自分)。

今回そのプラベを更に楽しむための、スプラトゥーンのブキ抽選ツール「イカすロット」をリリースしました。

作った

ikasu-lot.saihoooooooo.org

画面はこんな感じ。

f:id:saihoooooooo:20160206113344p:plain

イカすロットの特徴は次の4点です。

  • 最大8人同時のブキ抽選
  • 全ブキ、サブウェポンベース、スペシャルウェポンベース、カテゴリの4種類の抽選タイプ
  • ルール抽選機能もあり
  • 部屋のURLを共有することで同じページを見ている人とは抽選結果がリアルタイムに更新される

特に気に入ってるのが4番目の「部屋のURLを共有することで同じページを見ている人とは抽選結果がリアルタイムに更新される」です。
似たようなツールは既に存在していて、おおこれは便利と思ったものの共有の方法がスクリーンショットを撮って送る、とかでちょっと使いづらいなと思っていました。

また応用的な使い方として名前欄に「アルファチーム」、「ブラボーチーム」などと入力すればチーム単位でのブキ縛りをすることも可能です。
チャージャー4人vsローラー4人とか面白いんじゃないでしょうか。
もちろん名前をひとつだけ入力して、おみくじ感覚で今日のガチ仕様ブキを選んでいただいても構いません。

プラベ楽しいけど若干マンネリを感じている方、得意ブキに拘らず色んなブキを使ってみたい方は是非使ってみてください。



Meteorでの開発

さて、ここからは開発の話。
特に興味のない方は読む必要ありません。

使った技術
  • Meteor

これだけ、さすがフルスタック。
あとは強いて言うならデザインにMaterializeを使っているぐらいでしょうか。
Meteorは存在だけは知ってて使ってみたいなとは思ってたところ、今回の要件にバッチシはまったので採用することにしました。
(最初はexpress+socket.ioとかでやろうかとやんわり考えていました。)

Meteorの良いところ
  • フルスタック(さらにMongoDBも内蔵!)なのでインストールしてすぐ使える
  • DBやセッションのデータを更新するだけでviewにはリアクティブに反映される
  • ライブリロードも設定いらず
  • PaaSも用意されてる(!)ので小さいサービスならHerokuとかに金払わなくていい
  • 専用のパッケージシステム(Atmosphere)も用意されてる
Meteorの悪いところ
  • フルスタックなのでMeteor全てに依存する
  • デプロイが差分更新でないのでちょっとの修正でも時間がかかる(browserifyのWatchifyみたいなのが欲しい)
  • npmがそのまま使えない(使えるようにするパッケージはある)
  • 日本語の記事が少ないので英語力が低いとつらい

まぁ今回は極小サービスなので特に目立ったデメリットもなく楽しく開発できました。
実際の製品としての開発ならどうなんだろう、もうちょっと使い倒してみないとなんとも言えないですが、まだ時は来ていないのかもしれない。
やっぱこれも規模によるかな。

使ったパッケージ

最後に使ったパッケージをちょろっと書いておきます。

iron:router

基本。
ルーティングに使います。

Router.configure({
  layoutTemplate: 'layout',
  notFoundTemplate: 'notfound'
});

Router.route('/', function () {
  this.render('home');
});

Router.route('/article/:_id', {
  loadingTemplate: 'loading',
  waitOn: function () {
    return Meteor.subscribe('articles', this.params._id);
  },
  action: function () {
    this.render('article', {data: Articles.findOne()});
  }
});
mrt:moment-timezone

moment-timezoneのMeteor版。
使い方は特に変わらず。

moment().tz('Asia/Tokyo').format(); // 2016-02-06T04:00:00+09:00
mrt:cron

Meteor内でcron処理を実現できるパッケージ。

Meteor.startup(function () {
  var helloWorld = function () {
    console.log('hello world');
  }
  new Meteor.Cron({
    events: {
      "0 * * * *": helloWorld
    }
  });
});
sacha:spin

spin.jsのMeteor版。
こんなテンプレートを作って、上記iron:routerのloadingTemplateに指定してます。

<template name="loading">
  <div class="container">
    {{>spinner}}
  </div>
</template>
random

ランダムに関するパッケージ。

Random.id(10); // "Jjwjg6gouW"

Random.choice([1, 2, 3, 4, 5, 6]); // 6
check

簡易な値チェック。

Meteor.publish("chats-in-room", function (roomId) {
  check(roomId, String);
  return Chats.find({room: roomId});
});
spiderable

Meteorは基本bodyタグの中身がなく、サーバからのレスポンスでbody内をレンダリングしています。
これをクローラが見るとbody内が空のページだと認識されてしまうので、回避するためにこのパッケージを導入します。
ローカルで試す場合はphantomjsが必要(デプロイ先には用意済み)。

<head>
  <title>simple-todos</title>
  <meta name="fragment" content="!">
</head>

<body>
  {{> hello}}
</body>

<template name="hello">
  <h1>Welcome to Meteor!</h1>
</template>

この状態でhttp://your-domain.com/?_escaped_fragment_=にアクセスしてソースを確認した時にbodyタグ内が補完されていれば成功です。

materialize:materialize

MaterializeのMeteor用パッケージ。
特に何もしなくても入れさえすれば使えます。

まとめ

ダラダラと書いてきましたがイカすロット結構満足のいくものが出来たと思っております。
もしよければ使ってみてください!
Meteorも楽しいよ!

hubotでslackにスタンプ機能を追加してみた

slack Advent Calendar 2015 の25日目の記事になります。

はじめに

つい最近触れる機会がありちょろちょろっと使っているのですがいいですねslack。
見た目もいいし、アプリとの連携とか、コードスニペットを貼れたりとか。
また僕が特に気に入ってるものとしてemoji機能があります。
感情を一発で表現できるし自分で好きなemojiを登録することもできます。

ただこれもうちょっと大きく表示したいなー、って時がありまして、そうLINEのスタンプの様に。
この辺については去年のアドカレでも触れられているのですがまた別のアプローチで攻めてみたいと思います。

というわけで作った

github.com

インストール
$ npm install hubot-slack-stamp
$ vim external-scripts.json # 配列内に"hubot-slack-stamp"を追加
必要なもの
  • hubot
  • Node.js >= v4.0.0 (or --harmony or --harmony-generators flag)
  • PhantomJS
  • ImageMagick
設定
  • HUBOT_SLACK_STAMP_TEAM_NAME - slackのチーム名
  • HUBOT_SLACK_STAMP_EMAIL - ログイン用のメールアドレス
  • HUBOT_SLACK_STAMP_PASSWORD - ログイン用のパスワード

使ってみよう

チャンネル内のhubotに対してスタンプ名と画像URL、そして分割数を入力することでスタンプが登録されます。

hubot makestamp <name(a-z,0-9,_,-)> <image_url> <split_num(2-10)>

例としてはこんな感じ。

f:id:saihoooooooo:20151225030624p:plain

登録したスタンプを表示させる時は

hubot stamp <name(a-z,0-9,_,-)>

とします。
さっき登録したmarieちゃんを呼び出してみましょう。

f:id:saihoooooooo:20151225030636p:plain

いい感じですね!!

動作原理

slackのemojiは連続で入力すると特にパディングやマージンもなくピッチリ隙間なく表示されます。
この動きを利用して、

  1. 指定されたURLの画像を取得
  2. 画像をタイル上に分割
  3. 分割した画像を一枚一枚登録
  4. 綺麗に並べて表示

ということを行っています。
今回の例でいうと実際の文字列は以下のようになっています。

:marie_3x3_1-1::marie_3x3_1-2::marie_3x3_1-3:
:marie_3x3_2-1::marie_3x3_2-2::marie_3x3_2-3:
:marie_3x3_3-1::marie_3x3_3-2::marie_3x3_3-3:

emojiのカスタマイズページに行くと分割された画像が登録されているのがわかりますね。

f:id:saihoooooooo:20151225030640p:plain

イラスト: http://seiga.nicovideo.jp/seiga/im4981228

苦労した点、課題

slackにはまだemoji登録のAPIがないのでphantomjsを使ってスクレイピングするのがちょっとつらみでした。
はよこいAPI
参考URL:oti/slack-reaction-decomoji · GitHub

あと課題としては一応スタンプの削除機能も用意しているのですがこれがまた遅い。。
正直5x5枚の削除とかになってくると遅すぎて嫌になってきます。
この辺も整備したかったのですがちょっと時間が足りなかったのでまた公開後に。。

プルリクも絶賛お待ちしております。

おまけ

今回のスクリプトは企業対抗スプラトゥーン大会ことSplathon(スプラソン)の連絡事項をslack上で交わしている際に皆のemojiのやりとりを見て思いつきました。
githubリポジトリもsplathon名義であげています。
スプラトゥーンも楽しめて開発も楽しめるsplathon、最高すぎるぜ!
第2回(締め切り済み?)、第3回と予定しているようなので気になった方はチェックしてみてください。

最後に

今日は楽しいクリスマス〜♪
ということで12/25になりました。
slack Advent Calendar 2015を書かれた皆さん、お疲れ様でした。
それではHave a nice Xmas!!!!!