20190126のJavaScriptに関する記事は11件です。

AtomでESLintとPrettierをAirbnbルールで使う

はじめに

この記事では、エディタAtomを使ってJavaScriptのコードを書くときに、Airbnbのルールで構文エラー解析と自動フォーマットする方法をザックリとまとめています。

細かい説明はせず、私のような初心者を対象に、「ESLintって何?」というところから説明します。

なお私は詳しいことは理解できていなくて、今後同様の設定をするときに困らないようにするためのメモなので、間違いなど多々あるかもしれません。
その時はご指摘いただけると大変助かります!

なお、ここではAtomを使っていますが、VSCodeでも同様のことができるようです。
主に「プロジェクトにパッケージをインストール」する箇所と「エディタのプラグインパッケージをインストールする」箇所に分かれるので、後者をVSCodeの方で実行してもらえれば良いかもしれません(試していないですが、プロジェクト内では私以外はVSCodeでした)。

また、私はReact Nativeプロジェクトのためにこれらを使っていますが、JavaScriptならなんでも適用できると思います。

要約

話の流れの要約は以下です。

  1. ESLintで構文エラーを見つけられるようにする
  2. エラーの基準としてAirbnbの基準を採用する(なんか皆がオススメって言うので)
  3. エラーを一個一個手直しするのが面倒なので、Prettierを使ってオートフォーマットしてくれるようにする

まずESLintって何

ESLintはザックリ言うと、JavaScriptの構文解析ツールの1つです。
他にもあるらしいですが、これがよく使われているようです。

こいつがあると、コードを書いたときに、「ここが間違ってるぞ!!」と指摘してくれます。

例えば、

  • 「シングルクオートを使うべきところでダブルクオートを使ってるぞ!」
  • 「ここのスペースは余計だぞ!ここにスペース必要だぞ!」
  • 「ここはアロー関数を使え!」

などです。

こうした構文ルールをgitで管理してチームで共有することで、プロジェクト全体で構文の秩序を保つことができます。

なお、「何をもってして間違いとするかの基準」は自分で定義することもできるし、ESLintオススメの基準や、Airbnbが採用している基準を使うこともできます。
そうした基準を部分的に変えたり、特定のファイルで特定のルールだけ無効にすることなどもできます。

Airbnbのルールはこちらを参照ください。

ESLintの導入

まずは、パッケージをプロジェクトに追加します。

プロジェクトのルート階層で以下を実行します。

 yarn add eslint --dev
 //または
 npm install eslint --save-dev

Airbnbのルールを使う

次に、ESLintの初期設定をします

./node_modules/.bin/eslint --init

すると、

How would you like to configure ESLint?

と、下記のいずれのルールを採用するか親切に聞いてくれます

  • Airbnb
  • Standard
  • Google

ここで、Airbnbを選択しましょう。

img.jpeg

そのほか、

  • Reactは使う?
  • 設定ファイルの形式(json, js, ...)はなににする? <= 私はjsonにしました

などいくつか質問してくるので、環境に合わせて任意に答えてください。

ここで設定が終われば、 (先ほどjsonを指定した場合).eslintrc.json というファイルが自動で生成され、中身は以下のようになっているはずです。

{
    "extends": "airbnb"
}

設定の詳細は公式をご覧ください。

なお、私の環境ではESLintは v5.12.1を使っています。

./node_modules/.bin/eslint -v

ESLintで構文チェックをしてみる

ここまで設定が終われば、以下のように構文チェックするファイルを指定してあげることで、ターミナル上でエラー個所を指摘してもらえます。
(今回はindex.jsをチェック)

./node_modules/.bin/eslint index.js

img2.png

ただ毎回ターミナルで確認するのは面倒なので、ここでAtomにパッケージを追加してAtom上でエラー個所を表示できるようにします。

Atom上でESLintのエラーを確認する

Atomに、以下のパッケージを追加します。これだけです。

ただ、私の環境ではこの2つだけではエラーが表示されなかったので、以下を追加しました。

これで、Atom上でエラーが表示されるようになりました。
img3.png

Prettierで自動フォーマットをする

このままですと、エラー個所は表示されるのですが、1つ1つ手動で解決しなければなりません。

そこで、Prettierを使うことで、変更を保存する度に自動でフォーマットしてくれるようになります。1つ1つ手動で変えるところを、保存するだけで一発で変えてくれます。

詳細はPrettierの"Integrating with ESLint"で確認して欲しいですが、以下2つのコマンドを打ちます。

まずは、ESLintのルールで自動フォーマットしてもらうためにeslint-plugin-prettierを追加します。

yarn add --dev prettier eslint-plugin-prettier
//または
npm install --save-dev --dev prettier eslint-plugin-prettier

※なお、先ほどの公式ガイドには.eslintrc.jsonにも変更を加えていますが、次にもう1つ別のパッケージを追加するので、ここでは無視して大丈夫だと思います。

次に、ESLintのエラーとPrettierのエラーを重複させないために、 eslint-config-prettierを追加します。

yarn add --dev eslint-config-prettier
//または
npm install --save-dev eslint-config-prettier

そして、.eslintrc.json"plugins": ["prettier"]を追記します。

{
  "extends": ["airbnb"],
+ "plugins": ["prettier"]
}

ここまででPrettierの設定が終わったので、あとはオートフォーマットをAtom上で実行するために、prettier-atomをAtomに追加します。

すると、Atom内の下部にPrettierというボタンのようなものが表示され、そこを押して横にチェックマークが入れば準備完了。

img4.png

任意のファイルで保存コマンド(Macならcommand + s)を打てば、自動でフォーマットされるはずです。

自動フォーマットを解除したい時にはもう一度Prettierを押して非活性にします。

これで、目的だった「エディタAtomを使ってJavaScriptのコードを書くときに、Airbnbのルールで構文解析と自動フォーマット」が実現できたと思います!!

最後に

以上が、「エディタAtomを使ってJavaScriptのコードを書くときに、Airbnbのルールで構文エラー解析と自動フォーマット」のためのザックリとした説明でした。

ただ、ここでご紹介したのは、本当に基本的な部分です。

あとは、各自でルールを一部変更したり、特定ファイルで特定のルールを無効にしたりなど、各々調べて解決してもらいたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MTG アリーナのEXPORTしたデッキを日本語に変換するスクリプト

MTG Arenaのデッキをブログに貼り付けようとすると、プレイ中は絵で判別するし会話は日本語名でするので英語名だけ出されても自分でも読めない。

そこでアリーナからエクスポートしたテキストを貼り付ければ日本語のカード名に変換する処理を
MTG Developers
を使い作ろうと思いましたが、カードを一回で特定できないため断念中。

APIはOR検索ができるので、当初はカード名を全て取り出して渡せば一回の通信で済むと考えていました。
しかし、カード名での検索では、再録がそれぞれ1件となりページサイズを圧迫します。
基本土地だけで100件中のほとんどを使ってしまうのでよろしくない。
セット名まで指定した検索条件のグループ化までできればよかったのですが、そこまではできなさそう。
一応、APIにはカードごとに一意に割り当てられるハッシュがあるのですが、

id
A unique id for this card. It is made up by doing an SHA1 hash of setCode + cardName + cardImageName

cardImageNameが何を指すのか不明で使えず。そもそもエクスポート内に情報なし。

よって、カード一枚につき名前とセット名で一回一回通信する方法にしようとしましたが、
時折CORS要求に失敗したり、並列のためか数回実行するとCloudflareに弾かれたりと微妙。
そもそも待ち時間が気持ちよくない範囲になったので、素直に諦めてデッキビルダーを使ったほうがいいかなあという具合になりました。

処理時間が許容できてもエラーやセット名の違い等が今後も出そうで継続性がなさそうでした。

カード資産が集まれば4積みが増えてリクエスト数が減るのですが、初期無課金のレアをちょっとずつ積む構成やハイランダー、バベルなどに対して安定して処理を成功させることを考えると満足いく出来は難しいのかなと考えています。

コード

エラーがでなければちゃんと動く作りにはなっているので、遅くても良い場合はもうちょっと頑張ってGithub Pagesにでも投げるかもしれません。

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>

<textarea id="input" rows="20">
4 Novice Knight (M19) 30
24 Plains (RIX) 192
4 Righteous Blow (GRN) 23
4 Take Heart (GRN) 28
4 Leonin Vanguard (M19) 22
4 Daybreak Chaplain (M19) 10
4 Knight's Pledge (M19) 19
4 Oreskos Swiftclaw (M19) 31
4 Sunhome Stalwart (GRN) 26
4 Blade Instructor (GRN) 1
</textarea>

<select id="lang">
    <option value="Japanese">日本語</option>
    <option value=""></option>
    <option value=""></option>
    <option value=""></option>
    <option value=""></option>
</select>
<input id="leave" type="checkbox">
<button id="exe">変換</button>

<textarea id="out" rows='20'></textarea>

</body>
<script type="text/javascript">
/*
1 History of Benalia (DAR) 21
2 Squire's Devotion (RIX) 25
1 Legion's Landing (XLN) 22
4 Ajani's Pridemate (M19) 5
3 Ajani's Welcome (M19) 6
2 Cleansing Nova (M19) 9
4 Leonin Vanguard (M19) 22
3 Leonin Warleader (M19) 23
2 Resplendent Angel (M19) 34
4 Revitalize (M19) 35
4 Fountain of Renewal (M19) 235
1 Transmogrifying Wand (M19) 247
23 Plains (M19) 263
4 Dawn of Hope (GRN) 8
2 Divine Visitation (GRN) 10
*/

const input = document.getElementById('input')
const out = document.getElementById('out')
const exe = document.getElementById('exe')

const API_URL = 'https://api.magicthegathering.io/v1/cards'

let text

const makeQuery = (string) => {
 const reg = new RegExp('^(\\d+ )' + '(.*)' + ' \\((\\w+)\\) \\d+$', 'gm')
 const names = string.replace(reg, '$2').split('\n')
 // アリーナでのドミナリアのセット名がAPIと異なるので変換
 const sets = string.replace(reg, '$3').replace(/DAR/g, 'DOM').split('\n')
 const querys = []
 for (var i = names.length - 1; i >= 0; i--) {
   querys.push({
    'name' : names[i],
    'set'  : sets[i],
   })
 }
 return querys

 //console.log(names,names.replace(/$\n/mg, '|'))
 //return names.replace(/$\n/mg, '|')
}

const fetchInfo = (query) => {
     const params = new URLSearchParams();
     params.set('name', query.name);
     params.set('set',  query.set);
     params.set('pageSize', 1);

     // 原因不明で特定カードでのみ出そうなエラーがある。
     // クロスオリジン要求をブロックしました: 同一生成元ポリシーにより、https://api.magicthegathering.io/v1/cards?name=Legion%27s+Landing&set=XLN&pageSize=1 にあるリモートリソースの読み込みは拒否されます (理由: CORS 要求が成功しなかった)。[詳細]
     // TypeError: NetworkError when attempting to fetch resource.

     return fetch(API_URL + '?' + params.toString(), {
        mode: 'cors'
     })
      .then(response => {
        return response.json();
      })
      .then(json => {
        console.log(json);
        const lang = document.getElementById('lang').value
        const isLeaveOriginal = document.getElementById('leave').checked
        let replaceName
        for (const card of json.cards) {
         for (const foreignName of card.foreignNames) {
             if (foreignName.language == lang) {
                let replace
                 if (isLeaveOriginal) {
                     replace = '$1'+ foreignName.name + ' / ' + card.name +'    $2'
                 } else {
                     replace = '$1'+ foreignName.name +'$2'
                 }
                 console.log(text, replace)
                 text = text.replace(new RegExp('^(\\d+ )' + card.name + '( \\(\\w+\\) \\d+)$', 'm'), replace)
                 break
             }
         }
        }

      })
}

exe.addEventListener('click', async () => {
    text = input.value.trim()
    console.log(makeQuery(text))
    const querys = makeQuery(text)
    let requests = []
    for (const query of querys) {
        requests.push(fetchInfo(query))
    }
    await Promise.all(requests)
    out.textContent = text
    console.log(text, out)
})

</script>
</html>

関連

MTGのために画像と名前をさっとサジェストで検索する - Qiita

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vue.jsの引数に関してお聞きしたいです

自己解決しました。。。

どうやらjavascriptのreduce関数の仕様のようですね。
自力での調査が足りていませんでした

こんにちは。

vue.jsの勉強をしているのですが、javascriptの引数に関してわからないことがあるので質問します。

var items = [
    {
        name: '鉛筆†',
        price: 300,
        quantity: 0
    },
    {
        name: 'ノート',
        price: 400,
        quantity: 0
    },
    {
        name: '消しゴム',
        price: 500,
        quantity: 0
    }
]
var vm = new Vue({
    el: '#app',
    data: {
        items: items
    },
    filters: {
        numberWithDelimiter: function (value) {
            if(!value) {
                return '0'
            }
        return value.toString().replace(/(\d)(?=(\d{3})+$)/g, '$1,')
        }
    },
    computed: {
        totalPrice: function () {
        return this.items.reduce(function (sum,item) {
            return sum + (item.price * item.quantity)
        },0)
    },
    totalPriceWithTax: function() {
        return Math.floor(this.totalPrice * 1.08)
        }
    }   
})

以上が全体のソースコードになります。
この中の、

        totalPrice: function () {
        return this.items.reduce(function (sum,item) {
            return sum + (item.price * item.quantity)
        },0)

の箇所なのですが、
returnしている個所で"item.price"のようにプロパティの内容を引っ張ってきています。

なぜfunction(sum,item)の引数として渡されたitemに、
var itemsで宣言されたプロパティが対応しているのでしょうか?(変数名も一致していませんし...)

どなたか教えていただけると幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

nodeでcsvの読み書き

node.jsでcsvの入出力をする機会があったのでメモ。

やりたいこ

csvを読み込み、csvの特定のカラムに処理を加え、再度csvとして出力したい。
例えば下記のような感じ。

1,aaa,hoge
   ↓
1,AAA,hoge

2カラム目を大文字に変換して出力。

方針と準備

node-csvというモジュールがあるみたいなので、とりあえずそれを使ってみる。

npm install --save csv

テスト用のCSVファイルをindex.jsと同じ階層に作成。

test.csv
1,aaaa,hoge
2,bbbb,foo

実装

なんか独特の書き方。

index.js
const fs = require('fs');
const csv = require('csv');

//処理(跡でpipeに食べさせる)
const parser = csv.parse((error, data) => {

    //内容出力
    console.log('初期データ');
    console.log(data);

    //変換後の配列を格納
    let newData = [];

    //ループしながら1行ずつ処理
    data.forEach((element, index, array) => {
        let row = [];
        row.push(element[0]);
        row.push(element[1].toUpperCase()); //2カラム目を大文字へ
        row.push(element[2]);
        //新たに1行分の配列(row)を作成し、新配列(newData)に追加。
        newData.push(row);
    })

    console.log('処理データ');
    console.log(newData);

    //write
    csv.stringify(newData,(error,output)=>{
        fs.writeFile('out.csv',output,(error)=>{
            console.log('処理データをCSV出力しました。');
        })
    })
})

//読み込みと処理を実行
fs.createReadStream('test.csv').pipe(parser);

header行を読み飛ばすには下記のように、csv.parse()において、from_lineオプションを設定すればよい。

csv.parse({ from_line: 2 }, (error, data) => {})

実行してみる。

node index.js

出力結果。

out.csv
1,AAAA,hoge
2,BBBB,foo

その他

SJISとか読むならゴニョゴニョする必要があるらしい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

生のJavaScriptをjQueryで書き換えるだけ

勉強のために書いたら載せる。

idで指定したラジオボタンが選択されていたらitem表示するだけ

// js
var item;
(item = function(){
    item = document.getElementById("item");
    radio = document.getElementById("radio");
    if (radio.checked == false) {
        item.style.display = "none";
    } else if (radio.checked == true) {
        item.style.display = "";
    }
})();

// jQuery
$(function () {
    //ラジオボタンの初期状態を確認
    if ($("#radio").prop( 'checked' )) {
        $('#item').show();
    } else {
        $('#item').hide();
    }
    $("#radio").click(function () {
        $("#item").toggle();
    });
});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プログラマー向けの(?)数理論理学(8)選言に関する推論規則

プログラマー向けというか、数理論理学の枠組みをプログラムとして実装したい人とか形式化数学をやりたい人向けの記事かもしれません(形式化数学についてはこちらの記事もどうぞ)。

なお、ソースコード全体はGitHubにあります。

https://github.com/pizyumi/rena

連載まとめ

選言に関する推論規則

今回は選言に関する3つの推論規則について説明します。

自然演繹の場合、選言に関する推論規則は下の3つとなります。

  • 選言除去規則・・・命題P ∨ Qが正しいと証明されている時、命題Pを仮定した場合に命題Rが正しいと言え、かつ、命題Qを仮定した場合にも命題Rが正しいと言えるならば、命題Rも正しいと言える。ただし、命題Rの証明からは仮定Pと仮定Qが落ちる。
  • 選言導入規則(右)・・・命題Pが正しいと証明されている時、命題P ∨ Qも正しいと言える。
  • 選言導入規則(左)・・・命題Qが正しいと証明されている時、命題P ∨ Qも正しいと言える。

が「または」という意味であったことを考えればこれらの規則が妥当であることは理解できるかと思います。

選言除去規則は所謂場合分けです。命題P ∨ Qが成り立つと分かっている場合、命題Pと命題Qをそれぞれ仮定してみて同じ命題Rを結論することができれば命題P ∨ Qであるということから命題Rを結論できたということと同じということです。

推論規則の実装

ここから下は上の3つの推論規則をプログラムでどう実装するかという話なので、プログラミングに興味のない方は飛ばして次の記事に進んでください。


選言除去規則

最初に選言除去規則を適用する操作を特定の形の証明を作る操作としてcreate_disjunction_elimination関数において実装します。

function create_disjunction_elimination (pr1, pr2, pr3, pr4, pr5, dindex1, dindex2) {
  if (pr1.type !== 'proof') {
    throw new Error('not proof.');
  }
  if (pr2.type !== 'proof') {
    throw new Error('not proof.');
  }
  if (pr3.type !== 'proof') {
    throw new Error('not proof.');
  }
  if (pr4.type !== 'proof') {
    throw new Error('not proof.');
  }
  if (pr5.type !== 'proof') {
    throw new Error('not proof.');
  }
  if (pr4.subtype !== 'prem') {
    throw new Error('not premise.');
  }
  if (pr5.subtype !== 'prem') {
    throw new Error('not premise.');
  }

  if (pr1.conclusion.subtype !== 'disj') {
    throw new Error('not apply.');
  }
  if (!equal(pr1.conclusion.sub1, pr4.conclusion)) {
    throw new Error('not apply.');
  }
  if (!equal(pr1.conclusion.sub2, pr5.conclusion)) {
    throw new Error('not apply.');
  }
  if (!equal(pr2.conclusion, pr3.conclusion)) {
    throw new Error('not apply.');
  }

  var premises = [];
  var premises2 = [];
  gather_unique_obj(premises, pr1.premises);
  gather_unique_obj(premises, pr2.premises);
  gather_unique_obj(premises, pr3.premises);
  for (var i = 0; i < premises.length; i++) {
    if (!equal(premises[i], pr4.conclusion) && !equal(premises[i], pr5.conclusion)) {
      premises2.push(premises[i]);
    }
  }
  var conclusion = pr2.conclusion; //or pr3.conclusion

  var npr4 = shallowcopy(pr4);
  npr4.dindex = dindex1;
  replace(pr2, pr4, npr4);
  if (equal(pr2, pr4)) {
    pr2 = npr4;
  }

  var npr5 = shallowcopy(pr5);
  npr5.dindex = dindex2;
  replace(pr3, pr5, npr5);
  if (equal(pr3, pr5)) {
    pr3 = npr5;
  }

  return {
    type: 'proof',
    subtype: 'disj_elim',
    premises: premises2,
    conclusion: conclusion,
    sub1: pr1,
    sub2: pr2,
    sub3: pr3,
    sub4: npr4,
    sub5: npr5
  };
}

上で述べた選言除去規則(下に再掲)を忠実に実装しています。

  • 選言除去規則・・・命題P ∨ Qが正しいと証明されている時、命題Pを仮定した場合に命題Rが正しいと言え、かつ、命題Qを仮定した場合にも命題Rが正しいと言えるならば、命題Rも正しいと言える。ただし、命題Rの証明からは仮定Pと仮定Qが落ちる。

第1引数として上の規則における命題P ∨ Qの証明を表すオブジェクトを取り、第2引数として命題Pを仮定した場合における命題Rの証明を表すオブジェクトを取り、第3引数として命題Qを仮定した場合における命題Rの証明を表すオブジェクトを取り、第4引数として仮定Pを表すオブジェクトを取り、第5引数として仮定Qを表すオブジェクトを取ります。

そのため、第1引数の証明を表すオブジェクトの結論を表すオブジェクト(上の規則における命題P ∨ Qを表す)のsubtypeプロパティはdisjでなければなりません。

また、第1引数の証明を表すオブジェクトの結論を表すオブジェクトのsub1プロパティの値(上の規則における命題P ∨ Q中の命題Pを表す)と第4引数の仮定を表すオブジェクトの結論を表すオブジェクト(上の規則における仮定Pを表す)は同じでなければなりませんし、第1引数の証明を表すオブジェクトの結論を表すオブジェクトのsub2プロパティの値(上の規則における命題P ∨ Q中の命題Qを表す)と第5引数の仮定を表すオブジェクトの結論を表すオブジェクト(上の規則における仮定Qを表す)は同じでなければなりません。

また、第2引数の証明を表すオブジェクトの結論を表すオブジェクト(上の規則における命題Pを仮定した場合における命題Rを表す)と第3引数の証明を表すオブジェクトの結論を表すオブジェクト(上の規則における命題Qを仮定した場合における命題Rを表す)は同じでなければなりません。

そして、第6引数と第7引数は含意導入規則や否定導入規則や背理法の場合と同様に、この規則の適用によって落とされる仮定を表す2つのオブジェクトにそれぞれ付加される番号となります。

create_disjunction_elimination関数ではまず新たに作成する証明を表すオブジェクトの仮定と結論を作成しています。

仮定は第1引数と第2引数と第3引数の証明を表すオブジェクトの仮定を併せたものから第4引数と第5引数の仮定を表すオブジェクトの結論を取り除いたものとなります。

ただし、重複は取り除かなければなりませんので、そのためにgather_unique_obj関数を使用しています。

また、結論は第2引数の証明を表すオブジェクトの結論を表すオブジェクト(上の規則における命題Pを仮定した場合における命題Rを表す)となります。

あるいは、第3引数の証明を表すオブジェクトの結論を表すオブジェクト(上の規則における命題Qを仮定した場合における命題Rを表す)でも構いません。

次にcreate_disjunction_elimination関数では第4引数の仮定を表すオブジェクトをコピーします。このためにshallowcopy関数を使用しています。

そして、コピーしたオブジェクトにdindex1dindexプロパティとして与えます。

このままでは第2引数の証明を表すオブジェクトに含まれるオブジェクトがコピー前のオブジェクトである可能性がありますので、オブジェクトの置換を行います。このためにreplace関数を使用しています。

また、第2引数の証明を表すオブジェクト自体がコピー前のオブジェクトそのものである可能性もありますので、その場合にはコピー後のオブジェクトに置き換えます。

この操作を第5引数の仮定を表すオブジェクトと第3引数の証明を表すオブジェクトに対しても行います。ただし、dindex1の代わりにdindex2を使います。

そして、ここまでで作成したオブジェクトを使って新たな証明を表すオブジェクトを作成しています。

なお、選言除去規則の適用を表すオブジェクトのsubtypeプロパティの値はdisj_elimとすることにしました。

選言導入規則(右)

次に選言導入規則(右)を適用する操作を特定の形の証明を作る操作としてcreate_disjunction_introduction_right関数において実装します。

function create_disjunction_introduction_right (pr, p) {
  if (pr.type !== 'proof') {
    throw new Error('not proof.');
  }
  if (p.type !== 'prop') {
    throw new Error('not prop.');
  }

  var premises = [];
  gather_unique_obj(premises, pr.premises);
  var conclusion = create_disjunction(pr.conclusion, p);

  return {
    type: 'proof',
    subtype: 'disj_intro_r',
    premises: premises,
    conclusion: conclusion,
    sub1: pr,
    sub2: p
  };
}

上で述べた選言導入規則(右)(下に再掲)を忠実に実装しています。

  • 選言導入規則(右)・・・命題Pが正しいと証明されている時、命題P ∨ Qも正しいと言える。

第1引数として上の規則における命題Pの証明を表すオブジェクトを取ります。

また、第2引数として上の規則における命題P ∨ Q中の命題Qを表すオブジェクトを取ります。証明を表すオブジェクトではなく、数学的言明を表すオブジェクトであることに注意してください。

create_disjunction_introduction_right関数ではまず新たに作成する証明を表すオブジェクトの仮定と結論を作成しています。

仮定は第1引数の証明を表すオブジェクトの仮定となります。

ただし、重複は取り除かなければなりませんので、そのためにgather_unique_obj関数を使用しています。

そして、結論は第1引数の証明を表すオブジェクトの結論を表すオブジェクト(上の規則における命題Pを表す)と第2引数の数学的言明を表すオブジェクト(上の規則における命題P ∨ Q中の命題Qを表す)を材料として作成した選言を表すオブジェクト(上の規則における命題P ∨ Qを表す)となります。

そして、この仮定と結論を使って新たな証明を表すオブジェクトを作成しています。

なお、選言導入規則(右)の適用を表すオブジェクトのsubtypeプロパティの値はdisj_intro_rとすることにしました。

選言導入規則(左)

次に選言導入規則(左)を適用する操作を特定の形の証明を作る操作としてcreate_disjunction_introduction_left関数において実装します。

function create_disjunction_introduction_left (pr, p) {
  if (pr.type !== 'proof') {
    throw new Error('not proof.');
  }
  if (p.type !== 'prop') {
    throw new Error('not prop.');
  }

  var premises = [];
  gather_unique_obj(premises, pr.premises);
  var conclusion = create_disjunction(p, pr.conclusion);

  return {
    type: 'proof',
    subtype: 'disj_intro_l',
    premises: premises,
    conclusion: conclusion,
    sub1: pr,
    sub2: p
  };
}

上で述べた選言導入規則(左)(下に再掲)を忠実に実装しています。

  • 選言導入規則(左)・・・命題Qが正しいと証明されている時、命題P ∨ Qも正しいと言える。

第1引数として上の規則における命題Qの証明を表すオブジェクトを取ります。

また、第2引数として上の規則における命題P ∨ Q中の命題Pを表すオブジェクトを取ります。証明を表すオブジェクトではなく、数学的言明を表すオブジェクトであることに注意してください。

create_disjunction_introduction_left関数ではまず新たに作成する証明を表すオブジェクトの仮定と結論を作成しています。

仮定は第1引数の証明を表すオブジェクトの仮定となります。

ただし、重複は取り除かなければなりませんので、そのためにgather_unique_obj関数を使用しています。

そして、結論は第2引数の数学的言明を表すオブジェクト(上の規則における命題P ∨ Q中の命題Pを表す)と第1引数の証明を表すオブジェクトの結論を表すオブジェクト(上の規則における命題Qを表す)を材料として作成した選言を表すオブジェクト(上の規則における命題P ∨ Qを表す)となります。

そして、この仮定と結論を使って新たな証明を表すオブジェクトを作成しています。

なお、選言導入規則(左)の適用を表すオブジェクトのsubtypeプロパティの値はdisj_intro_lとすることにしました。

今回はここまで

今回はここまでとします。次回は同値に関する3つの推論規則について説明します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

bootstrapで複数言語にサイトを対応させる

wordpressサイトで4ヶ国語に対応するという案件があったので、メモメモ。。。

GOAL

今回は静的コンテンツ(html)の中のテキストのみ、選択した言語に切り替えます。

image

スクリーンショット 2019-01-26 14.12.29.png

How to

ドロップダウンのhref="#****"の箇所と、切り替えボックスのidを対応させることがコツです。

test.html
<!-- ドロップダウンメニュー -->
<div class="lang-menu-wrp">
        <ul class="nav nav-pills">
            <li class="dropdown">
                <a class="dropdown-toggle" data-toggle="dropdown" href="#">Language <span class="caret"></span></a>
                <ul class="dropdown-menu">
                    <li><a href="#lang-english" data-toggle="tab">English</a></li>
                    <li><a href="#lang-chinese" data-toggle="tab">Chinese</a></li>
                    <li><a href="#lang-korean" data-toggle="tab">Korea</a></li>
                    <li><a href="#lang-japanese" data-toggle="tab">Japanese</a></li>
                </ul>
            </li>
        </ul>
</div>


<!-- 切り替えボックス -->
<div class="tab-content">
 <div class="tab-pane" id="lang-english">
  <p>text english</p>
 </div>
 <div class="tab-pane" id="lang-chinese">
  <p>text 中国</p>
 </div>
 <div class="tab-pane" id="lang-korean">
  <p>text 한국</p>
 </div>
 <div class="tab-pane" id="lang-japanese">
  <p>text japanese</p>
 </div>
</div>


参考サイト

http://bootstrap3.cyberlab.info/javascript/tab-pills-dropdown.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Google Apps Scriptで対数近似を計算するスクリプトを作った

やりたいこと

Googleスプレッドシートでは入力した数値からグラフを作成し、グラフを近似する曲線と方程式を描画できる。

しかし、この方程式はテキストとしてコピーできない。(2019/1/25時点の筆者調べ。)
不便なのでGASを使って、セルの数値からグラフを対数近似する関数を計算し、表示されている方程式と同じものをテキストとしてセルに出力するコードを書いた。

問題設定


こんな感じになっているとする。
xの列はグラフの横軸、yの列はグラフの縦軸に対応する。
入力xとそれに対する出力yの組が5個あるというデータ。
このデータの関係を表現する式を導出したい。
(右のグラフにはすでにgoogleが計算してくれた式が出ている。これをテキストで欲しい。)

ちなみに、xの最小値が1になっているのは、今回作成したコードではxに0が来るとlog(0)を計算し、結果がNanになるため。まだ対策できていない。
(今年初めまでのSpread Sheetではgoogleの計算も0を回避していた(データ点そのものが無視された)記憶があるんだけど、今は普通に計算されている。↓の画像参照。)

xに0を入力した場合。googleは計算でき、結果が上の画像と違う。今の筆者のコードではできない。

解き方

1. 最小二乗法を使う

このベストアンサーをコードに起こした。
リンク先の説明を拝借しながら説明すると、

$y=a\ln x+b$ が当てはめるべき式。
$X=\ln x$として、

$y_1, X_1$
$y_2, X_2$
$\vdots$
$y_n, X_n$

の値の組を得る。つまりこれらの組はそれぞれ、

$y_1=aX_1+b$
$y_2=aX_2+b$
$\vdots$
$y_n=aX_n+b$

という関係。
最小二乗の原理から

E=\sum_{i=1}^{n} ( aX_i+b-y_i )^2 \tag{1}

を最小にするa, bを求めればよいので、
$(1)$をaで偏微分して0とおき

\begin{align}
\frac{\partial E}{\partial a}=\sum_{i=1}^n 2 X_i (a X_i+b-y_i)&=0 \\
a\sum_{i=1}^n X_i^2+b\sum_{i=1}^nX_i&=\sum_{i=1}^nX_iy_i \tag{2}
\end{align}

同様にbに関して

\begin{align}
\frac{\partial E}{\partial b}=\sum_{i=1}^n 2 (a X_i+b-y_i)&=0 \\
a\sum_{i=1}^n X_i+n b &=\sum_{i=1}^n y_i \tag{3}
\end{align}

が得られる。
$(2)$$(3)$は正規方程式といい、連立させて解けばa,bが得られる。
元記事の回答執筆者candy712様、ありがとうございます。

2. 逆行列で連立方程式を解く

上記$(2)$$(3)$を眺めてみると、

\begin{align}
a\sum_{i=1}^n X_i^2+b\sum_{i=1}^nX_i&=\sum_{i=1}^nX_iy_i \tag{2} \\
a\sum_{i=1}^n X_i+n b &=\sum_{i=1}^n y_i \tag{3}
\end{align}
\begin{align}
a(何かスカラー)+b(何かスカラー)&=(何かスカラー) \tag{2} \\
a(何かスカラー)+(何かスカラー)b &=(何かスカラー) \tag{3}
\end{align}

という形をしている。
つまり、2行2列の行列$A$と、a,bをまとめたベクトル$x$の積、および右辺の2個のスカラーをまとめたベクトル$c$の関係として

Ax=c

のように書ける。

(xが何回も出てきて紛らわしいが)$x$の中身であるa,bを求めたいので、$A$の逆行列$A^{-1}$(存在すると仮定する)を用いて、

\begin{align}
A^{-1}Ax&=A^{-1}c \\
x&=c'
\end{align}

$c'$は$A^{-1}c$を置き換えたもので、2要素のベクトルとなる。もともと

x=
\begin{pmatrix}
a  \\
b 
\end{pmatrix}

であったので、これでa,bが求められた。

コード

逆行列を計算する関数はt.hira様のJavaScript で逆行列と行列式の値を求めるからお借りした。掃き出し法を使用されている。
また、少数第一位で四捨五入する関数を@nagito25様の【JavaScript】桁指定して四捨五入・切り上げ・切り捨てからお借りした。
ありがとうございます。この場を借りてお礼申し上げます。

function appx_log(){
  //sheet名を指定
  var ss = SpreadsheetApp.getActive().getSheetByName('log_approx');
  //47行3列目から5行分取得
  var r_range=47;
  var c_range=3;
  var get_range=5;
  var range = ss.getRange(r_range,c_range,get_range);
  var values=range.getValues();

  var NUM_POINTS=5; // データ点数が5個
  var xs=[1, 50, 100, 150, 200]; //横軸(x軸)の値
  var Xs = [];            
  var ys=[];
  var sum_ys=0;
  var sum_Xs=0;             
  var sum_Xsys=0;             
  var sum_sq_Xs=0;

  //yの配列とyの和を準備
  for(var row in values){
    for(var col in values[row]){
      var val=values[row][col];
      Logger.log(val);
      ys.push(val);
      sum_ys+=val;
    }
  }

  //Xの配列とXの和、Xの2乗の和、Xとyの積の和を準備
  for (var i =0; i<xs.length; i++){
    var val=Math.log(xs[i]);
    Logger.log(val);
    Xs.push(val);         
    sum_Xs+=val;
    sum_sq_Xs+=Math.pow(val,2);
    sum_Xsys+=val*ys[i];
  }

  //係数行列Aとその逆行列を作る
  var A=[];
  var order=2;
  for (var i=0; i<order; i++){
    A[i]=new Array(order);

  }
  A[0][0]=sum_sq_Xs;
  A[0][1]=sum_Xs; 
  A[1][0]=sum_Xs;
  A[1][1]=NUM_POINTS;  
  var inv=inverse(A);

  //a, bを計算する
  var a=inv[0][0]*sum_Xsys+inv[0][1]*sum_ys;
  var b=inv[1][0]*sum_Xsys+inv[1][1]*sum_ys; 
  a=orgRound(a,10);
  b=orgRound(b,10);

  //"10+-20.5lnx"のような変な表記を回避
  var ret;
  if(a<0){
    ret=String(b)+ String(a)+"lnx";
  }else{
    ret=String(b)+"+"+ String(a)+"lnx";

  }

  Logger.log("result:%s", ret);
  //取得した5個のデータの下のセルに出力
  ss.getRange(r_range+get_range,c_range).setValue(ret);


}


//http://thira.plavox.info/inverse/
function inverse(a){
  var c=a.length; //行列の次数
  var buf; //一時的なデータを蓄える
  var i,j,k; //カウンタ

  //単位行列を作る
  var inv=new Array(c);
  for(i=0;i<c;i++){
    inv[i]=new Array(c);
    for(j=0;j<c;j++){
      inv[i][j]=(i==j)?1.0:0.0;
    }
  }
  //掃き出し法
  for(i=0;i<c;i++){
    buf=1/a[i][i];
    for(j=0;j<c;j++){
      a[i][j]*=buf;
      inv[i][j]*=buf;
    }
    for(j=0;j<c;j++){
      if(i!=j){
        buf=a[j][i];
        for(k=0;k<c;k++){
          a[j][k]-=a[i][k]*buf;
          inv[j][k]-=inv[i][k]*buf;
        }
      }
    }
  }

  return inv;
}

//https://qiita.com/nagito25/items/0293bc317067d9e6c560
function orgRound(value, base) {
    return Math.round(value * base) / base;
}

実行


[ツール]->[スクリプトエディタ]からGASのエディタを開く。

実行する関数(右の丸)を選択し、実行ボタン(左の丸)を押す。
ちなみに

[表示]->[ログ]からLogger.log()で出力させたログが見れる。


方程式がセルに出力され、googleの計算結果と一致している。

まとめ

  • 方程式をテキストで得るために、GASで対数近似を計算するスクリプトを書いた。
  • 最小二乗法と逆行列による解法を使用した。
  • 現時点ではxに0が含まれる場合は計算できない

展望

  • xに0が含まれる場合に対応すること
  • べき級数近似も欲しい。$y=ax^{b}$へのあてはめを目的に非線形最小二乗法を使う気がしている。(全然違うかもしれない)
  • 今回の$X=\ln x$とおいて$y=aX+b$で計算するというのは、カーネル法的な考え方なんじゃないか。(全然違うかもしれない)

なお、筆者はjavascriptの文法を知らない。GASは今回初めて使用した。数学も素人なので、突っ込みなどお待ちしています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

obnizつかってJavaScriptでラジコンカー作った

前提

2018年夏ごろ「obniz」とかいうオモチャを発見したので、買って遊んだ時のハナシ

「obniz」とは

obniz
https://obniz.io/
お手軽にIoT製品とか作れる!すごいオモチャ!!
専用クラウドサービス(obnizクラウド)も使い放題!1
Wi-fi経由でobnizクラウドに接続し、JavaScriptで制御できるすごいヤツ!
読み方は「オブナイズ」らしいよ!

どうやって遊ぶか

出来るだけ簡単で、作った後に遊べそうなモノにしたかったので
ラジコンカー!2(よくあるやつ)

obniz以外で必要なモノ

モバイルバッテリー+USBケーブル

モバイルバッテリー+USBケーブル
obnizの電源にはモバイルバッテリーを使用できます。
緊急時にコンビニで買った乾電池を使用するタイプを使用しました。

DCモーター+タイヤ

DCモーター+タイヤ
DCモーターはミニ四駆とかに入ってる直流電源で動くモーターで、
今回はDCモーターとギアボックスとタイヤが合体してる奴を使いました。

キャスター

キャスター
DCモーター4個も買いたくなかったので、FF駆動(ロントエンジン・フロントドライブ)とし、
後輪はキャスター(100均)を使用しました。

ジャンパーワイヤー

ジャンパーワイヤー
オスメスの端子がついてる電線で、DCモーターをobnizに繋ぐのに使用しました。
(DCモーターの端子に、オス端子のある線を半田付け)

シャーシ

カラーボード(100均)を使用

その他

飽きたら解体するので各パーツの接着は、両面テープを使用しました。

ハードウェア完成品

本体
割とカッコいい?

ソフトウェアで使ったライブラリー

obniz javascript SDK

これだけでobniz制御できる!すごい!!
https://obniz.io/doc/obnizjs_doc

JQuery

みんな大好き!Vue.js!
https://jquery.com/

JCanvas

HTML5 CanvasをJQueryで操作する奴(初めて使った!)
https://projects.calebevans.me/jcanvas/

obnizによるDCモーター制御部分

取り合えずobnizにモバイルバッテリーとDCモーターを接続
obnizに接続

マニュアルみて、書いてみたらめっちゃ簡単に動いた!
https://obniz.io/sdk/parts/DCMotor/README.md?iframe=false

宣言回り
/** obniz sdk */
let obniz = new Obniz();
/** 左モーター */
let leftMotor = null;
/** 右モーター */
let rightMotor = null;

/**
 * obniz接続
 */
obniz.onconnect = async () => {
  // ハードウェア初期設定
  leftMotor = obniz.wired("DCMotor", {forward: 0, back: 1}); // 左モーター
  rightMotor = obniz.wired("DCMotor", {forward: 2, back: 3}); // 右モーター
DCモーター制御用の関数
/**
 * モーター制御
 * param {number} leftPower 左モーターパワー
 * param {number} leftMove 左モーター回転方向
 * param {number} rightPower 右モーターパワー
 * param {number} rightMove 右モーター回転方向
 */
let motorControl = (leftPower, leftMove, rightPower, rightMove) =>  {
  // モーターが設定されていなければ中断
  if (!leftMotor || !rightMotor) return;

  leftMotor.power(leftPower);
  rightMotor.power(rightPower);

  switch(leftMove) {
    case MOTER_MOVE_FORWARD:
      leftMotor.forward();
      break;
    case MOTER_MOVE_REVERSE:
      leftMotor.reverse();
      break;
    default:
      leftMotor.stop();
  }

  switch(rightMove) {
    case MOTER_MOVE_FORWARD:
      rightMotor.forward();
      break;
    case MOTER_MOVE_REVERSE:
      rightMotor.reverse();
      break;
    default:
      rightMotor.stop();
  }
}

画面操作系

あまりに簡単に動いたんで、暇つぶしに画面操作部分を少し凝ってみました。
機能としては

  • PCのマウスでも、スマホのタッチでも操作可能
  • スマホの縦向き、横向きどちらでも操作可能
  • タッチしている間だけ表示される、バーチャルパッドのスティック風

ちなみにJCanvasを使ってCanvasへの作画はこんな感じでです。

バーチャルパッドスティック作画の関数
/**
 * バーチャルパッドスティック作画
 * param {number} x X座標
 * param {number} y Y座標
 */
let drawVirtualPadStick = (x, y) => {
  $("#VirtualPad")
    .removeLayerGroup("VirtualPadStick")
    .drawLayers()
    .drawArc({layer: true,
              name: 'StickCircle',
              groups: ['VirtualPadStick'],
              strokeStyle: 'rgba(0, 0, 0, 0.5)',
              fillStyle: 'rgba(0, 0, 0, 0.5)',
              strokeWidth: 1,
              radius: STICK_RADIUS,
              x: x - $("#VirtualPad").offset().left - STICK_RADIUS,
              y: y - $("#VirtualPad").offset().top - STICK_RADIUS
     }); // スティックの円
}

バーチャルパットのサンプル(HTMLとして保存したら動くハズ)
バーチャルパッドのサンプル
<!DOCTYPE html>
<html lang="jp">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <style>
      body {
        margin: 0;
        overflow: hidden;
      }
      canvas {
        position: absolute;
      }
    </style>
    <title>Virtual Pad Sample</title>
  </head>
  <body>
    <canvas id="Screen"></canvas>
    <canvas id="VirtualPad"></canvas>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jcanvas/21.0.1/min/jcanvas.min.js" integrity="sha256-rUshvLY805GcMilbPNnko2JxFKj254/GJZwIP6yaiEk=" crossorigin="anonymous"></script>
    <script>
      // jCanvasの座標の基準を左上に設定
      $.jCanvas.defaults.fromCenter = false;

      /** スティックの閾値 */
      const STICK_THRESHOLD = 150;
      /** スティックの半径 */
      const STICK_RADIUS = 20;

      /** 初期X座標 */
      let defaultX = -1;
      /** 初期Y座標 */
      let defaultY = -1;

      /**
       * タッチデバイス判定
       * return {boolean} 判定結果
       */
      let isTouchDevice = () => window.ontouchstart === null;

      /**
       * 閾値に丸める
       * param {number} value 丸める前の値
       * param {number} threshold 閾値
       * return {number} 丸めた後の値
       */
      let roundToThreshold = (value, threshold) => {
        let result;

        if (value > threshold) {
          result = threshold;
        } else if (value < threshold * -1) {
          result = threshold * -1;
        } else {
          result = value
        }

        return result;
      }

      /**
       * バーチャルパッドベース作画
       * param {number} x X座標
       * param {number} y Y座標
       */
      let drawVirtualPadBase = (x, y) => {
        $("#VirtualPad")
          .drawArc({layer: true,
                    name: 'CenterCircle',
                    groups: ['VirtualPadBase'],
                    strokeStyle: 'rgba(0, 0, 0, 0.5)',
                    strokeWidth: 1,
                    radius: STICK_RADIUS,
                    x: x - $("#VirtualPad").offset().left - STICK_RADIUS,
                    y: y - $("#VirtualPad").offset().top - STICK_RADIUS
           }) // 中央の円
          .drawRect({layer: true,
                     name: 'FrameRect',
                     groups: ['VirtualPadBase'],
                     strokeStyle: 'rgba(0, 0, 0, 0.5)',
                     fillStyle: 'rgba(0, 0, 0, 0.05)',          
                     strokeWidth: 1,
                     cornerRadius: STICK_RADIUS,
                     x: x - $("#VirtualPad").offset().left - STICK_THRESHOLD - STICK_RADIUS,
                     y: y - $("#VirtualPad").offset().top - STICK_THRESHOLD - STICK_RADIUS,
                     width: STICK_THRESHOLD * 2 + STICK_RADIUS * 2,
                     height: STICK_THRESHOLD * 2 + STICK_RADIUS * 2
           }) // 枠の四角
          .drawLine({layer: true,
                     name: 'TopLine',
                     groups: ['VirtualPadBase'],
                     strokeStyle: 'rgba(0, 0, 0, 0.5)',
                     strokeWidth: 1,
                     rounded: true,
                     x1: x - $("#VirtualPad").offset().left,
                     y1: y - $("#VirtualPad").offset().top - STICK_THRESHOLD,
                     x2: x - $("#VirtualPad").offset().left,
                     y2: y - $("#VirtualPad").offset().top - STICK_THRESHOLD - STICK_RADIUS
           }) // 上線
          .drawLine({layer: true,
                     name: 'BottomLine',
                     groups: ['VirtualPadBase'],
                     strokeStyle: 'rgba(0, 0, 0, 0.5)',
                     strokeWidth: 1,
                     rounded: true,
                     x1: x - $("#VirtualPad").offset().left,
                     y1: y - $("#VirtualPad").offset().top + STICK_THRESHOLD,
                     x2: x - $("#VirtualPad").offset().left,
                     y2: y - $("#VirtualPad").offset().top + STICK_THRESHOLD + STICK_RADIUS,
           }) // 下線
          .drawLine({layer: true,
                     name: 'LeftLine',
                     groups: ['VirtualPadBase'],
                     strokeStyle: 'rgba(0, 0, 0, 0.5)',
                     strokeWidth: 1,
                     rounded: true,
                     x1: x - $("#VirtualPad").offset().left - STICK_THRESHOLD,
                     y1: y - $("#VirtualPad").offset().top,
                     x2: x - $("#VirtualPad").offset().left - STICK_THRESHOLD - STICK_RADIUS,
                     y2: y - $("#VirtualPad").offset().top
           })  // 左線
          .drawLine({layer: true,
                     name: 'RightLine',
                     groups: ['VirtualPadBase'],
                     strokeStyle: 'rgba(0, 0, 0, 0.5)',
                     strokeWidth: 1,
                     rounded: true,
                     x1: x - $("#VirtualPad").offset().left + STICK_THRESHOLD,
                     y1: y - $("#VirtualPad").offset().top,
                     x2: x - $("#VirtualPad").offset().left + STICK_THRESHOLD + STICK_RADIUS,
                     y2: y - $("#VirtualPad").offset().top
           }); // 右線

        // バーチャルパッドスティック作画
        drawVirtualPadStick(x, y);
      }

      /**
       * バーチャルパッドスティック作画
       * param {number} x X座標
       * param {number} y Y座標
       */
      let drawVirtualPadStick = (x, y) => {
        $("#VirtualPad")
          .removeLayerGroup("VirtualPadStick")
          .drawLayers()
          .drawArc({layer: true,
                    name: 'StickCircle',
                    groups: ['VirtualPadStick'],
                    strokeStyle: 'rgba(0, 0, 0, 0.5)',
                    fillStyle: 'rgba(0, 0, 0, 0.5)',
                    strokeWidth: 1,
                    radius: STICK_RADIUS,
                    x: x - $("#VirtualPad").offset().left - STICK_RADIUS,
                    y: y - $("#VirtualPad").offset().top - STICK_RADIUS
           }); // スティックの円
      }

      /**
       * バーチャルパットクリア
       */
      let clearVirtualPad = () => {
        $("#VirtualPad")
          .removeLayerGroup("VirtualPadBase")
          .removeLayerGroup("VirtualPadStick")
          .drawLayers();
      }

      /**
       * ロード、画面回転、サイズ変更
       */
      $(window).on("load orientationchange resize", () => {
        // バーチャルパッド用キャンバスを画面サイズに合わせる
        $("#VirtualPad").get(0).width = $(window).width();
        $("#VirtualPad").get(0).height = $(window).height();

        // 画面用キャンバスを画面サイズに合わせる
        $("#Screen").get(0).width = $(window).width();
        $("#Screen").get(0).height = $(window).height();

        // バーチャルパッドクリア
        clearVirtualPad();
      });

      /**
       * バーチャルパッド用キャンバス上でタッチ開始、マウスダウン
       */
      $("#VirtualPad").on("touchstart mousedown", () => {
        event.preventDefault();

        // 初期座標取得
        defaultX = isTouchDevice() ? event.changedTouches[0].pageX : event.pageX;
        defaultY = isTouchDevice() ? event.changedTouches[0].pageY : event.pageY;

        // バーチャルパッドベース作画
        drawVirtualPadBase(defaultX, defaultY);

        /**
         * バーチャルパッド用キャンバス上でタッチ移動、マウス移動
         */
        $("#VirtualPad").on("touchmove mousemove", () => {
          event.preventDefault();

          // 移動後の座標取得
          let tempX = roundToThreshold(defaultX - (isTouchDevice() ? event.changedTouches[0].pageX : event.pageX), STICK_THRESHOLD);
          let tempY = roundToThreshold(defaultY- (isTouchDevice() ? event.changedTouches[0].pageY : event.pageY), STICK_THRESHOLD);

          // バーチャルパッドスティック作画
          drawVirtualPadStick(defaultX - tempX, defaultY - tempY);
        });

        /**
         * バーチャルパッド用キャンバス上でタッチ終了、マウスアップ、マウスが離れた
         */
        $("#VirtualPad").on("touchend mouseup mouseleave", () => {
          event.preventDefault();

          $("#VirtualPad").off("touchmove mousemove");

          // バーチャルパッドクリア
          clearVirtualPad();
        });
      });
    </script>
  </body>
</html>

んで、obnizによるDCモーター制御部分のソースを組み込んで、ちょいちょい弄って終了。

ソフトウェア完成

操作画面

動かしたらこんな感じ
操作イメージ

ソースコード

http://obniz.io/program?root=/users/286/repo&filename=DoubleMotorCarController.html
(obniz Cloudのエディターが開きます。)

実走

実走
めっちゃタイヤ滑った…

感想

めっちゃ簡単にできた!すごい!!
本当はカメラモジュール買ったんで組み込んで遠隔操作できるようにしたかったんだけど、
動作確認がてら遊んでたらカメラモジュール壊れちゃった…
このあとで、ドットマトリックスLEDで遊んだし、
スマートリモコンもどき作ろうと思って、パーツ買ってあるのでまだまだ遊べそう。


  1. 本体価格にobnizクラウドの永久使用料も含まれてるらしい。 

  2. Wi-fiもラジオだし間違っちゃない…ハズ 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Kindleでハイライトした内容をScrapboxに書き込むためのメモ

  • Kindle Notebookを開く
  • ブックマークに追加して、編集のURLに以下のJavaScriptコードをコピペする
    • your-project-nameを自分のScrapboxのプロジェクト名に変更する
ブックマークレット
 javascript:
Scrapbox="https://scrapbox.io/your-project-name/";
url="https://read.amazon.co.jp/kp/notebook";
if(location.href.indexOf(url) == 0) {
  var titleElement=document.getElementById("annotation-scroller").querySelectorAll("h3.kp-notebook-metadata");
  var title =titleElement[0].innerText;

  var authorElement=document.getElementById("annotation-scroller").querySelectorAll(".a-spacing-none");
  var authors = authorElement[0].innerText;  
  var content ="";

  var highlightNodes = document.getElementsByClassName("kp-notebook-highlight");
  var highlights = [];

  var quotationNodes = document.querySelectorAll("#annotationHighlightHeader");
  var quotationPages = [];

  for (var i = 0; i < highlightNodes.length; i++) {
    highlights.push(highlightNodes[i].innerText);
  }

  for (var i = 0; i < quotationNodes.length; i++) {
    quotationPages.push(quotationNodes[i].innerText);
  }

  var banner = "[" + document.querySelector(".kp-notebook-printable img").src + " " + document.querySelector(".kp-notebook-printable").href + "]";  
  content += banner;
  content += "\n";
  content += "By: " + authors;  
  content += "\n\n";

  for(var i = 0; i < quotationPages.length; i++) {
    content += quotationPages[i];
    content += "\n";
    content += ">" + highlights[i];
    content += "\n";
  }

  var links = "#readings" + " ";  
  authorList = authors.split('、');
  for (var i = 0; i < authorList.length; i++) {
    links += "#" + authorList[i] + " ";
  }
  content += links;

  url = Scrapbox + encodeURIComponent(title) + "?body=" + encodeURIComponent(content);
}

location = url;

Scrapboxに追加したい本を選択して、ブックマークをクリックすると自動的にScrapboxに追加される。

参考

W&R : Jazzと読書の日々

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javascriptで画像リサイズ(縮小)

画像アップロード機能を実装するときによく使うのでメモします。

/**
 * @param {HTMLImageElement} imgEL 
 * @param {Number} maxSize 
 * @return {Promise<Blob>}
 */
const resizeImage = (imgEL, maxSize, type = 'image/png') => 
  new Promise((resolve) => {
    const ratio = imgEL.naturalWidth / imgEL.naturalHeight
    const canvas = document.createElement('canvas')
    canvas.width = ratio >= 1 ? maxSize : maxSize / ratio
    canvas.height = ratio < 1 ? maxSize : maxSize / ratio
    canvas.getContext('2d').drawImage(imgEL, 0, 0, canvas.width, canvas.height)
    canvas.toBlob(resolve, type)
  })

戻り値のBlobが縮小されたやつでそのままサーバにアップロードすればいい感じですね。

使用例

<body>
  <input type="file">
  <div id="preview"></div>
  <button>アップロード</button>
  <script>
    const resizeImage = (imgEL, maxSize, type = 'image/png') =>
      new Promise(resolve => {
        const ratio = imgEL.naturalWidth / imgEL.naturalHeight
        const canvas = document.createElement('canvas')
        canvas.width = ratio >= 1 ? maxSize : maxSize / ratio
        canvas.height = ratio < 1 ? maxSize : maxSize / ratio
        canvas.getContext('2d').drawImage(imgEL, 0, 0, canvas.width, canvas.height)
        canvas.toBlob(resolve, type)
      })

    const pFileReader = blob =>
      new Promise(resolve => {
        const fr = new FileReader()
        fr.readAsDataURL(blob)
        fr.onload = e => resolve(e.target.result)
      })

    const pImage = src =>
      new Promise(resolve => {
        const img = new Image()
        img.src = src
        img.onload = e => resolve(img)
      })

    document.querySelector('input').onchange = async e => {
      const file = e.target.files[0]
      const preview = document.querySelector('#preview')
      const src = await pFileReader(file)
      const img = await pImage(src)
      // 本当に縮小されることを確認したい場合は以下をコメントアウト
      // const blob = await resizeImage(img, 100, file.type)
      // img.src = await pFileReader(blob)
      preview.appendChild(img)
    }

    document.querySelector('button').onclick = async () => {
      const file = document.querySelector('input').files[0]
      const src = await pFileReader(file)
      const img = await pImage(src)
      const blob = await resizeImage(img, 100, file.type)
      // blobをアップロード ~
    }
  </script>
</body>

以上!

FileReader使ってますけど, URL.createObjURLで代用できますよ。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む