投稿者「shoarai」のアーカイブ

ぷよぷよプログラミングで右回転を実装する

前回の記事で、セガが提供している「ぷよぷよプログラミング」を紹介しました。

公式のソースコードでは、左回転はできますが右回転ができません。右回転できるようソースコードを改変します。

ぷよぷよのゲーム画面

左回転の処理を理解する

右回転できるようにするためには、すでに実装されている左回転のコードをコピーして修正すれば実現できるはずです。まずは、ソースコードを読んで左回転の処理を理解します。

前の記事でも説明していますが、game.js の loop() の中で、mode が”playing”のとき(プレイヤーの操作を判定している箇所)と、 “rotating”のとき(ぷよを回転している箇所)のコードを調べます。Player.playing() が “rotating” を返した場合に、Player.rotating() で回転していることがわかります。Player.playing() と Player.rotating() の中を見てみましょう。

function loop() {
    switch (mode) {
     ...中略...
        case "playing":
            // プレイヤーが操作する
            const action = Player.playing(frame);
            mode = action; // 'playing' 'moving' 'rotating' 'fix' のどれかが帰ってくる
            break;
     ...中略...
        case "rotating":
            if (!Player.rotating(frame)) {
                // 回転が終わったので操作可能にする
                mode = "playing";
            }
            break;

Player.playing() の中を見てみると、左回転を行う↑キーの入力判定があります。そして、操作するぷよの現在の回転角度に応じて cx, cy, canRotate をセットしています。cx は回転後の横移動の量を、cy は縦移動の量を表します。例えば、操作するぷよが左端にあるときに左回転を行うと、操作するぷよ全体を右に1つずらす必要があり、このときに cx が 1 になります。操作するぷよの左右が壁や別のぷよで囲まれている場合は、回転できないと判断し、canRotate を false にしています。canRotate が true の場合のみ、”rotating” を呼び出し元に返しています。

    static playing(frame) {
        ...中略...
        } else if (this.keyStatus.up) {
            ...中略...
            const rotation = this.puyoStatus.rotation;
            let canRotate = true;

            let cx = 0;
            let cy = 0;
            if (rotation === 0) {
                ...中略...
            }

            if (canRotate) {
                ...中略...
                return "rotating";
            }
        }

Player.rotating() の中を見ると、ぷよの現在の回転角度である this.puyoStatus.rotation に 90° を足していることがわかります。ratio が 1 になるまでは回転の途中のため false を返しており、ratio が 1 になり 90° 回転し終わったときに true を返しています。

    static rotating(frame) {
        // 回転中も自然落下はさせる
        this.falling();
        const ratio = Math.min(1, (frame - this.actionStartFrame) / Config.playerRotateFrame);
        this.puyoStatus.left = (this.rotateAfterLeft - this.rotateBeforeLeft) * ratio + this.rotateBeforeLeft;
        this.puyoStatus.rotation = this.rotateFromRotation + ratio * 90;
        this.setPuyoPosition();
        if (ratio === 1) {
            this.puyoStatus.rotation = (this.rotateFromRotation + 90) % 360;
            return false;
        }
        return true;
    }

ここまでで、左回転を行っている処理をざっくり理解できました。ここからは、右回転の処理を実装していきます。必要な処理を一度に実装するとミスに気づきにくいため、以下の手順で段階的に実装していきます。

  1. 左回転を↑キーからzキーに変更する
  2. xキーでも左回転できるようにする
  3. xキーで右回転できるようにする
  4. 単純に右回転できない場合の処理を追加する

左回転を↑キーからzキーに変更する

元のソースコードでは、↑キーを押すとぷよが左回転します。これをzキーで左回転、xキーで右回転するよう実装します。まずは↑キーの入力判定しているところを、zキーに置き換えます。zキーのキーコードは仕様によると 90 です。keyStatusオブジェクトのキーであるupをrotateLeftに名称変更し、e.keyCodeの条件を 38 から 90 に変更します。下記のソースコード以外の箇所もすべて変更します。

    static initialize() {
        // キーボードの入力を確認する
        this.keyStatus = {
            right: false,
            left: false,
            rotateLeft: false,
            down: false,
        };
        // ブラウザのキーボードの入力を取得するイベントリスナを登録する
        document.addEventListener("keydown", (e) => {
            // キーボードが押された場合
            switch (e.keyCode) {
                ...中略...
                case 90: // zキー
                    this.keyStatus.rotateLeft = true;
                    e.preventDefault();
                    return false;
                ...中略...

変更したソースコードを動かしてみましょう。↑キーは反応しなくなり、zキーで左回転できれば成功です。

xキーでも左回転できるようにする

左回転の既存の処理をそのまま利用して、xキーでも左回転できるようにします。this.keyStatus.rotateLeft に関するコードをコピーして、その直後に貼り付けます。xキーのキーコードは 88 です。下記のソースコード以外の箇所もすべて変更します。

    static initialize() {
        // キーボードの入力を確認する
        this.keyStatus = {
            right: false,
            left: false,
            rotateLeft: false,
            rotateRight: false,
            down: false,
        };
        // ブラウザのキーボードの入力を取得するイベントリスナを登録する
        document.addEventListener("keydown", (e) => {
            // キーボードが押された場合
            switch (e.keyCode) {
                ...中略...
                case 90: // zキー
                    this.keyStatus.rotateLeft = true;
                    e.preventDefault();
                    return false;
                case 88: // xキー
                    this.keyStatus.rotateRight = true;
                    e.preventDefault();
                    return false;
                case 39: // 右向きキー
                ...中略...

以下の this.keyStatus.rotateLeft で条件分岐している箇所も、まずはコピーして貼り付け、rotateRight に書き換えます。

    static playing(frame) {
        ...中略...
        } else if (this.keyStatus.rotateLeft) {
          ...中略...
        } else if (this.keyStatus.rotateRight) {
          ...中略...
        }
        ...中略...

変更したソースコードを動かしてみましょう。xキーを入力しても、zキーと同じように左回転すれば成功です。

xキーで右回転できるようにする

左回転は 90° ですが、右回転は -90° です。回転する角度を変えられるよう、Player.rotating() で 90° と記載されている箇所を変数 this.rotateAngle に置き換えます。以下のコードで this.puyoStatus.rotation に360の余りを代入していることから、this.puyoStatus.rotation は 0 ~ 360° である必要があるようです。this.rotateAngle が -90°のときに this.puyoStatus.rotation がマイナスにならないように、360を足す処理を加えています。

    static playing(frame) {
            ...中略...
        } else if (this.keyStatus.rotateLeft) {
                ...中略...
                this.puyoStatus.x += cx;
                this.rotateAngle = 90;
                const distRotation = (this.puyoStatus.rotation + this.rotateAngle) % 360;
                // 変更前:const distRotation = (this.puyoStatus.rotation + 90) % 360;
                ...中略...
    }
    static rotating(frame) {
        ...中略...
        this.puyoStatus.rotation = this.rotateFromRotation + ratio * this.rotateAngle;
        // 変更前:this.puyoStatus.rotation = this.rotateFromRotation + ratio * 90;
        this.setPuyoPosition();
        if (ratio === 1) {
            this.puyoStatus.rotation = (this.rotateFromRotation + this.rotateAngle + 360) % 360;
            // 変更前:this.puyoStatus.rotation = (this.rotateFromRotation + 90) % 360;
            return false;
        }
        return true;
    }

変更したソースコードを動かしてみましょう。zキーで左回転、xキーで右回転すれば成功です。

これで完成かと思いきや、この状態だと問題があります。ぷよを一番右に動かしてから、xキーを教えてみましょう。なんと、右回転したぷよが隠れてしまいます。期待する動きとしては、右回転時にぷよ全体が左に動くべきです。他にも、ぷよが一番左にあるときに右回転すると、ぷよ全体が右に移動してしまうといった問題もあります。次はこれらの問題を修正します。

右回転前の画面

xキー入力

右回転後の画面

単純に右回転できない場合の処理を追加する

ぷよの現在の回転角度に応じて、ぷよを左右や上下にずらす処理が必要です。以下のように、もともと左回転の処理であるコードを変更します。右回転の処理は、左回転の処理を左右反対にすればいいので、角度は左右反対に(0°→180°、180°→0°)、x軸はプラスマイナス反対に(x-1→x+1、x+1→x-1、cx=1→cx=-1)変更します。

    static playing(frame) {
        ...中略...
        } else if (this.keyStatus.rotateRight) {
            ...中略...
            if (rotation === 180) {
                // 右から上には100% 確実に回せる。何もしない
            } else if (rotation === 90) {
                // 上から右に回すときに、右にブロックがあれば左に移動する必要があるのでまず確認する
                if (y + 1 < 0 || x + 1 < 0 || x + 1 >= Config.stageCols || Stage.board[y + 1][x + 1]) {
                    if (y + 1 >= 0) {
                        // ブロックがある。右に1個ずれる
                        cx = -1;
                    }
                }
                // 左にずれる必要がある時、左にもブロックがあれば回転出来ないので確認する
                if (cx === -1) {
                    if (y + 1 < 0 || x - 1 < 0 || y + 1 >= Config.stageRows || x - 1 >= Config.stageCols || Stage.board[y + 1][x - 1]) {
                        if (y + 1 >= 0) {
                            // ブロックがある。回転出来なかった
                            canRotate = false;
                        }
                    }
                }
            } else if (rotation === 0) {
                // 右から下に回す時には、自分の下か右下にブロックがあれば1個上に引き上げる。まず下を確認する
                if (y + 2 < 0 || y + 2 >= Config.stageRows || Stage.board[y + 2][x]) {
                    if (y + 2 >= 0) {
                        // ブロックがある。上に引き上げる
                        cy = -1;
                    }
                }
                // 右下も確認する
                if (y + 2 < 0 || y + 2 >= Config.stageRows || x + 1 < 0 || Stage.board[y + 2][x + 1]) {
                    if (y + 2 >= 0) {
                        // ブロックがある。上に引き上げる
                        cy = -1;
                    }
                }
            } else if (rotation === 270) {
                // 下から左に回すときは、左にブロックがあれば右に移動する必要があるのでまず確認する
                if (y + 1 < 0 || x - 1 < 0 || x - 1 >= Config.stageCols || Stage.board[y + 1][x - 1]) {
                    if (y + 1 >= 0) {
                        // ブロックがある。左に1個ずれる
                        cx = 1;
                    }
                }
                // 右にずれる必要がある時、右にもブロックがあれば回転出来ないので確認する
                if (cx === 1) {
                    if (y + 1 < 0 || x + 1 < 0 || x + 1 >= Config.stageCols || Stage.board[y + 1][x + 1]) {
                        if (y + 1 >= 0) {
                            // ブロックがある。回転出来なかった
                            canRotate = false;
                        }
                    }
                }
            }

            if (canRotate) {
            ...中略...

これで右回転に必要な処理はすべて実装できました。ぷよが縦の状態で一番右にあるとき、一番左にある時、ぷよが下に回転する状態で別のぷよが右下にある時、などおかしな挙動にならないか確認しましょう。

おわりに

実装済みの左回転を参考にすれば、右回転も簡単に実装できるかと思っていましたが、意外と必要な処理が多く、理解することや変更する箇所が多くて大変でした。今回の変更内容は以下のGitHubのコミット履歴で確認できます。

https://github.com/shoarai/puyopuyo-programing/commit/1a1484d7f56bdaa1ad2c917320817d84a92436df

おまけ:VSCode・フォーマッタ・Webサーバ設定

公式の開発環境ではMonacaが利用できます。自分は使い慣れたVSCodeを使いたかったため、右回転の処理を追加する前に以下の設定を行いました。

以下のVSCodeのワークスペースファイルを、ルートフォルダに作成します。ファイル保存時に自動フォーマットできるよう、フォーマッタであるPrettierを使用します。このファイルをVSCodeで開いて実装します。

{
    "folders": [
        {
            "path": "."
        }
    ],
    "settings": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
    },
    "extensions": {
        "recommendations": ["esbenp.prettier-vscode"]
    }
}

以下のPrettierの設定ファイルもルートフォルダに作成します。自分のPCのディスプレイ幅に合わせて一行の文字数上限を設定し、元のソースコードと同じタブ幅を設定します。

printWidth: 150
tabWidth: 4

アプリの起動のために、簡易的なWebサーバであるhttp-serverを使います。以下のファイルに”start”コマンドとして追加します。

{
    ...中略...
    "scripts": {
        "monaca:preview": "npm run dev",
        "dev": "browser-sync start -s www/ --watch --port 8080 --ui-port 8081",
        "start": "npx http-server www"
    },
    ...中略...
}

ターミナルで以下のコマンドを実行すると、アプリが起動します。

npm start

ぷよぷよプログラミングのソースコードを理解する

作りたい落ちゲーがあり、落ちゲーの作り方を調べていました。自分にとっての落ちゲーといえば、ぷよぷよ。そこで見つけたのが、公式のセガが提供している「ぷよぷよプログラミング」です。落ちゲーの作り方を学ぶため、ぷよぷよプログラミング」のソースコードを理解したいと思います。

ソースコードは以下の公式サイトからダウンロードできます。

https://puyo.sega.jp/program_2020

アプリ画面

以下は実際のアプリ画面です。←/→キーで左/右に移動でき、↑キーで左回転できます。右回転はできないようです。アプリを起動すると思わずやってしまいますね。やっぱりぷよぷよは楽しい。

ぷよぷよのゲーム画面

起動時の処理

www/src/game.jsで、Webページの読み込みが完了したときに initialize() と loop() を実行しています。initialize() はデータの初期化であったり、キーボード入力時の処理の登録を行っているようです。loop() でゲームを開始しています。

// 起動されたときに呼ばれる関数を登録する
window.addEventListener("load", () => {
    // まずステージを整える
    initialize();

    // ゲームを開始する
    loop();
});

loop() の最後の行で、requestAnimationFrame() を実行しています。これにより、loop() が繰り返し実行されます。1/60秒後に呼び出すとコメントがありますが、仕様によると、多くのブラウザではディスプレイのリフレッシュレートに合わせて呼び出されるようです。

loop() の繰り返しの中で行う処理を、modeの値に応じて切り替えています。

function loop() {
    switch (mode) {
        case "start":
            // 最初は、もしかしたら空中にあるかもしれないぷよを自由落下させるところからスタート
            mode = "checkFall";
            break;
        case "checkFall":
            // 落ちるかどうか判定する
            if (Stage.checkFall()) {
                mode = "fall";
            } else {
        ...中略...
    }
    frame++;
    requestAnimationFrame(loop); // 1/60秒後にもう一度呼び出す
}

modeの状態遷移

modeの値がどのように変化するのかわかりやすくするため、状態遷移図に起こしてみました。

modeの状態遷移図

アプリ実行時には、ぷよはステージ上に一つもありません。よって、start → checkFall → checkErase → newPuyoへと特に何も処理をせずに遷移します。newPuyo で新しいぷよを作成し、playing でプレイヤーの操作を受け付ける状態になります。操作がなければ playing のままですが、移動や回転操作があれば moving か rotating に変わります。操作するぷよがステージの床に着いたら fix になり、checkFall に戻ります。

checkFall では、自由落下するぷよがあるかを判定し、fall で自由落下の描画を行います。自由落下とは、下段のぷよを消して上段のぷよが降ってくるなどの状態のことです。checkErase では、削除するぷよがあるかを判定し、erasing でぷよの削除を行います。

newPuyo で新しいぷよが作成できないと gameOver になり、batankyu となってばたんきゅーという文字を画面に描画します。

今後やりたいこと

mode の状態遷移がわかったことで、全体としてどんな処理を行っているのかが理解できました。今回久しぶりにぷよぷよをやったのですが、またハマりそうです。せっかくソースコードを理解したので、機能を追加していくのも楽しいかなと思っています。

Teamsのマイクミュートをワンクリックで切り替える方法

Microsoft TeamsでWeb会議をよくするのですが、マイクのミュートの切り替えが面倒に感じていました。会議中にTeamsとは別のウィンドウを開いていることが多く、Teamsのウィンドウをアクティブに戻してからミュートを切り替えるのが非常に手間でした。

今回は、Teamsのウィンドウがアクティブでなくても、ワンクリックでマイクのミュートを切り替える方法を紹介します。実施環境のOSはWindowsです。

設定手順

まずAutoHotKeyのv2.0をインストールします。以下のURLを開いてDowloadボタンからインストーラをダウンロードして、インストールします。

https://www.autohotkey.com/

ToggleMuteTeams.ahk という名前のファイルを任意の場所に作成し、以下のスクリプトを記載します。.ahkファイルはAutoHotKeyのスクリプトファイルです。

F1::toggle_mute_teams()
toggle_mute_teams() {
winList := WinGetlist("ahk_exe Teams.exe",,,)
for window in winList {
if (WinGetTitle(window) != "Microsoft Teams Notification" and WinGetTitle(window) != "") {
WinActivate(WinGetTitle(window))
Send("^+M")
sleep 20 ; sometimes it activates the new window before the sendkeys finishes, delay fixes it
break ; mute shortcut works in both windows, so break after first send
}
}
}

作成した ToggleMuteTeams.ahk をダブルクリックして起動します。これで、会議中に「F1」キーを押してTeamsのマイクミュートを切り替えられるようになりました。Teamsのウィンドウがアクティブでない状態で「F1」キーを押すと、Teamsのウィンドウがアクティブになってからマイクミュートが切り替わります。

PCを起動し直した場合は、このスクリプトを再度実行する必要があります。ToggleMuteTeams.ahk のショートカットを以下のフォルダに配置することで、PC起動時に実行されるようになります。

C:\Users\<ユーザー名>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup

トラックボールのボタンへのキー割り当て

「F1」キーを押すことでマイクのミュートを切り替えられるようになりましたが、もっと簡単に、トラックボールのボタンで切り替えられるようにしました。

自分はLogicoolのトラックボールを使っていて、Logi Options+というPCアプリでボタンの動作をカスタマイズできます。トラックボールのミドルクリック(スクロールホイールを押す動作)に「F1」キーボタンを割り当てました。これにより、トラックボールから手を離さずにミドルクリックを押すことで、マイクのミュートが切り替えられるようになりました。

Logi Optionsの設定画面

ミュート切り替えボタンの変更

「F1」キーを押すには両手を使う必要があり、間違って押す可能性が低いため「F1」キーを設定しました。上記スクリプトの1行目を書き換えることで、別のキーを割り当てることもできます。AutoHotKeyのキーリストから設定できるキー名が確認できます。例えば、Insertキーに割り当てたい場合は、以下のように記載します。スクリプトを変更した場合は、変更を反映するために ToggleMuteTeams.ahk を再実行してください。

Insert::toggle_mute_teams()

補足

Teamsはチャットのウィンドウと会議のウィンドウが別に開きます。どちらのウィンドウがアクティブになっているのかAutoHotKeyは判定できないため、上記のスクリプトでは両方のウィンドウをアクティブにしてマイクミュート切り替えのボタン「Ctrl + Shift + M」を入力しています。現状では、チャットのウィンドウではこのボタンを押しても何も起こらないため問題ないですが、今後意図しない動作になる可能性があるため注意が必要です。

AutoHotKeyのスクリプトは以下のスクリプトを参考にしました。
https://github.com/randyklein/muteteams/blob/main/TeamsMute-ahkv2.ahk

麻雀 点数計算

麻雀の点数計算をするための、最低限の情報をまとめる。

点数表

ロンの点数

1翻2翻3翻4翻
30符1,000
1,500
2,000
2,900
3,900
5,800
7,700
11,600
40符1,300
2,000
2,600
3,900
5,200
7,700
満貫
50符1,600
2,400
3,200
4,800
6,400
9,600
満貫
青字は親の点数。翻を-1、符を×2した点数は同じ点数。)
5翻6, 7翻8, 9, 10翻11, 12翻13翻
満貫跳満倍満3倍満数え役満
8,00012,00016,00024,00032,000
12,00018,00024,00036,00048,000
青字は親の点数。親は子の1.5倍)

ツモの点数

子のツモ:
・親が支払う点数:ロンの点数 ÷ 2
・子が支払う点数:親が支払う点数 ÷ 2
※十の位が0にならないときは、十の位を切り上げる。

親のツモ:
・子が支払う点数:ロンの点数 ÷ 3
※3で割り切れないときは、+100や+200して割り切れるようにする。

基本符20
アガり方10:面前ロン
or
0:鳴きロン
or
2:ツモ
面子の種類4:2 ~ 8牌の暗刻、1・9・字牌の明刻
槓子なら×2、鳴きなら÷2
アタマ2:自風牌、場風牌、白發中
待ちの種類2:両面待ち・シャボ待ち以外

面前鳴き補足
立直(リーチ)1
一発(イッパツ)1
門前清自摸和(メンゼンツモ)1
平和(ピンフ)1符は必ず20符
雀頭は役牌以外
両面待ちのみ
一盃口(イーペーコー)1
役牌(ヤクハイ)11
断么九(タンヤオ)11
海底撈月(ハイテイ)11
河底撈魚(ホウテイ)11
嶺上開花(リンシャンカイホウ)11
槍槓(チャンカン)11
両立直(ダブルリーチ)2
七対子(チートイツ)2符は必ず25符
一気通貫21
三色同順21
混全帯幺九(チャンタ)21
三色同刻(サンショクドーコー)22
三暗刻22
対々和(トイトイ)22
三槓子(サンカンツ)22
小三元 (ショウサンゲン)22
混老頭(ホンロウトウ)22么九牌(数牌の1、9と字牌)のみ使う
二盃口(リャンペーコー)3一盃口を2組揃える
純全帯公九(ジュンチャン)32老頭牌(1・9牌)をすべての面子と雀頭に使う
混一色(ホンイツ)32
清一色(チンイツ)65

役満

役満補足
四暗刻(スーアンコウ)
大三元(ダイサンゲン)
国士無双(コクシムソウ)
小四喜(ショースーシー)東南西北のうち、3種類を刻子または槓子、残り1種を雀頭に使う
大四喜(ダイスーシー)東南西北のすべてを刻子で揃える
緑一色(リューイーソウ)索子の23468と發のみを使う
字一色(ツウイーソウ)字牌だけで雀頭も面子も揃える
清老頭(チンロウトウ)老頭牌(1・9牌)だけで雀頭も面子も揃える
四槓子(スーカンツ)
九蓮宝燈(チューレンポウトウ)1112345678999+同種の数牌1枚
地和(チーホウ)子が第一ツモでアガる
天和(テンホウ)親が第一ツモでアガる

自作のWebアプリをAndroidアプリ化できなかった

先日、風邪ログというWebアプリをリリースしました。自分の体調を記録して振り返り、自分がどんなときに体調が悪くなるかを理解できるアプリです。Webアプリですが、Google Playストアでもアプリを見つけてもらえるように、Androidアプリ化できるか試してみました。Androidアプリ化の方法はいくつかありますが、今回は一番さくっとできそうなWebViewを使った方法を試しました。

風邪ログのカレンダー画面
風邪ログの体調入力画面

WebViewで動かしてみる

WebViewを使ってWebアプリを動かす方法を、Android公式の記事を参考に試しました。

https://developer.android.com/guide/webapps/webview?hl=ja

まずはAndroid Studioで新規プロジェクトを作成し、Empty ActivityをTemplateとして選びます。この初期状態でアプリを実行すると、空の画面が表示されます。

まずはactivity_main.xmlにWebViewを記載します。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <!-- 以下のブロックを記載 -->
    <WebView
        android:id="@+id/webview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

次に、インターネット上で動くWebアプリにアクセスできるよう、AndroidManifest.xmlに許可設定を記載します。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.isolity.kazelog">

    <!-- 以下のブロックを記載 -->
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        ...

最後に、MainActivity.ktにWebViewの設定を記載します。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 以降の行を記載
        val myWebView: WebView = findViewById(R.id.webview)
        // Webアプリ内のリンクをWebView内で開くようにする
        myWebView.webViewClient = WebViewClient()
        // JavaScriptの有効化
        myWebView.settings.javaScriptEnabled = true
        // Web Storageの有効化
        myWebView.settings.domStorageEnabled = true
        // Webアプリの読み込みのため、風邪ログのURLを指定
        myWebView.loadUrl("https://kazelog.shoarai.com")
    }
}

上記のコードでアプリを実行すると、風邪ログのトップページが表示されました。

WebView上でGoogleログインできない

ここでやっかいなのが、以下の処理です。

// Webアプリ内のリンクをWebView内で開くようにする
myWebView.webViewClient = WebViewClient()

風邪ログは、EmailによるログインやGoogleアカウントによるログインをサポートしています。ログインをボタンを押したときに、Googleのログイン画面に遷移することになるんですが、上記の設定を行わないとGoogle ChromeなどのAndroid規定のブラウザでログイン画面が開いてしまいます。上記の設定をするとこで、ログイン画面がWebView内で開くようになります。

さてここで本題なのですが、GoogleはWebView内でのログインをサポートしていません。たとえWebView内でGoogleにログインしようとしても、以下のエラー画面が表示されます。

認証エラー画面

この件に関する公式のドキュメントを探したところ、以下の記事が見つかりました。やはりWebViewからGoogleログインに必要なOAuthリクエストが許可されなくなるとのことです。

「ウェブビュー」と言われる埋め込みブラウザから Google への OAuth リクエストは許可されなくなります。

https://developers-jp.googleblog.com/2016/09/modernizing-oauth-interactions-in-native-apps.html

ということでWebアプリである風邪ログを、Androidアプリとしてリリースすることはできませんでした。Googleログインを行わないWebアプリであれば、リリースできるかもしれませんね。

源泉徴収票に書いてあること

源泉徴収票

は、勤務先から受け取る給与や賞与の1年間の合計金額です。
給与を渡す会社側の視点なので”支払”金額となってますが、受け取る側から見ると”収入”金額になります。1年間の収入なので、いわゆる「年収」はこれのことです。

は、収入のうち税金を計算する前の金額です。
の金額」と「以下の表」を使って求められます。

画像の例でいうと、は 400万円 なので、以下の表の5行目にあたります。これを計算すると、「400万円 ✕ 20% + 44万円 = 124万円」です。からこの金額を引くと、になります。

 440万円()ー 124万円 = 216万円(

収入金額給与取得控除額
〜 162.5万円55万円
162.5万円 〜 180万円収入金額 ✕ 40% ー 10万円
180万円 〜 360万円収入金額 ✕ 30% + 8万円
360万円 〜 660万円収入金額 ✕ 20% +44万円
660万円 〜 850万円収入金額 ✕ 10% + 110万円
850万円 〜 195万円
給与所得控除額

は、安くなる税金の金額です。
保険料を払っていたり、子供がいる場合に税金が安くなることがあります。

画像の例でいうと、「の金額」と48万円を足すとになります。

 90万円()+ 48万円 = 138万円(

※この48万円は年によって金額が違います。2020年から48万円ですが、それ以前は38万円です。

は、払う税金の金額です。
この金額は、ベースとなる税金(所得税)と、東日本大震災からの復興のための税金(復興特別所得税)の合計です。
復興特別所得税は、2013年から2037年までかかります。

まずはこれまでの金額を使って、税金の対象となる金額を計算します。画像の例でいうと、「216万円()ー 138万円()= 78万円」です。次に、以下の表を使って所得税を計算します。

 78万円 ✕ 5% = 3万9,000円(所得税)

課税所得金額税率控除額
〜 195万円5%なし
195万円 〜 330万円10%9万7,000円
330万円 〜 695万円20%42万7,000円
695万円 〜 900万円23%63万6,000円
所得税の速算票(一部抜粋)

そして、所得税から復興特別所得税を計算します。

 3万9,000円(所得税) ✕ 2.1% = 819円復興特別所得税)

所得税と復興特別所得税を足すと、になります。

 3万9,000円(所得税) + 819円(復興特別所得税)= 3万9,819円
                         → 3万9,800円(
                         (100円未満は切り捨て)

※本記事は、2021年11月1日時点の情報です。税金の金額や計算式は、今後変わる可能性があります。

M5StickC Plusで在宅勤務用の環境モニターを作った

先日、M5StickC Plusを使ってCO2を測りました。この記事ではその続きとして、温度、湿度、気圧も測りました。

完成形

完成形はこちらです。M5EnvMonitorと名付けました。

M5EnvMonitorの画面

ソースコードはGitHubで公開しています。

https://github.com/shoarai/M5EnvMonitor

開発過程

CO2の計測までは、以下の記事で紹介しています。

追加の部品

PlatformIOの環境構築

以前は開発環境としてArduino IDEを使っていたのですが、今回からPlatformIOとVisual Studio Code(VSCode)を使うことにしました。フォーマットの自動化やGitとの連携をしたかったのが理由です。

1. VSCodeと、PlatformIOというVSCode拡張機能をインストールする。

2. PlatformIOを開き、Home画面のNew Projectボタンから新規プロジェクトを作成する。

  • Name: プロジェクト名。ここではM5EnvMonitorとする。
  • Board:M5Stick-C(M5Stack)
  • Framework:Arduino
  • Location:任意のディレクトリ

これでプロジェクトが作成できました。この時点でGitを初期化、コミットしておきます。

Env IIの利用

Libraries画面で必要なプラグインを検索し、プロジェクトにインストールする。

ライブラリのインストールは、platform.ioファイルのlib_depsに直接記載することも可能です。また、Arduinoライブラリとして公開されていなくても、GitHubで公開されているライブラリをインストールすることもできます。以下の最後の行のように、GitHubのURLを記載すればOKです。

[env:m5stick-c]
platform = espressif32
board = m5stick-c
framework = arduino
lib_deps =
	m5stack/M5StickCPlus@^0.0.2
	adafruit/Adafruit Unified Sensor@^1.1.4
	adafruit/Adafruit BMP280 Library@^2.4.2
	ambientdatainc/Ambient ESP32 ESP8266 lib@^1.0.1
	https://github.com/nara256/mhz19_uart

ENV IIの利用

ENV IIのサンプルコードを使って、ENV IIの測定値を表示します。Adafruit_Sensor.hはAdafruit Unified Sensorライブラリに含まれていたので、ライブラリをインストールしました。ENVII.inoの内容をsrc/main.cppコピーしてビルド、アップロードします。

カスタマイズ

計測値の表示レイアウトを調整したり、M5StickC Plusの情報表示を追加しました。
※CO2センサーが壊れているので、繋いでいない状態です;なのでCO2は-1になってます。

M5EnvMonitorの画面

画面仕様は以下の通り。

  • 画面右上:充電状態。”Full”は満充電。”USB”は充電中。
  • 画面右上:電池の残量。
  • 画面左の色:測定値が快適かどうか。緑なら問題なし。赤は問題あり。
          ・17℃ < 温度 < 28℃なら緑、それ以外は赤
          ・40% < 湿度 < 60なら緑、それ以外は赤
  • 画面右下:Ambientへのデータ送信の結果。成功なら表示なし、失敗なら”Send NG”

使っていくうちにまたいろいろ変えるかもしれません。

WordPressをAMP化し、Gistの埋め込みに対応した

このサイトはWordPressを使って作成していますが、表示速度の向上やSEO対策のためにAMP化しました。

WordPressのAMP化

AMP化は、以下のWordPress公式プラグインを使って簡単に実現できました。

AMP

またGoogle Analyticsの設定は、「AMP → 設定 → アナリティクス」の「種類:」に「googleanalytics」と入力します。すると「JSON構成:」が自動入力されるので、「UA-」で始まるトラッキングIDを設定すればOKです。

しかし、AMP化によって一つ問題が発生しました。AMP化したことにより、Gistの埋め込みコードが表示されなくなってしまったのです。

GistのAMP対応

Gistの埋め込みコードを、AMP化したWordPressに表示する方法として、ショートコードを使う方法を紹介します。そのために、JetpackというこれまたWordPress公式プラグインを使います。

Jetpackの「設定 → 執筆」にある「ショートコードを使って作成し、人気サイトからメディアを埋め込む」にチェックを入れます。これにより、ショートコードの機能が使えるようになります。

AMP化前は、カスタムHTMLで以下のコードを記載すればGistを埋め込めました。

<script src="https://gist.github.com/shoarai/a98cf027914d43139f4dbe579f726b8b.js"></script>

AMP化後は、ショートコードで以下のテキストを記載すればGistを埋め込めます。

[gist]a98cf027914d43139f4dbe579f726b8b[/gist]

ちなみに、ショートコードはGist以外にも、Facebookの投稿やTwitterのツイートなども埋め込めます。利用可能なショートコードは以下で確認できます。
https://jetpack.com/support/shortcode-embeds/

M5StickC PlusとAmbientでCO2の推移を可視化した

前回の記事では、M5StickC PlusとMH-Z19CでCO2濃度を測定し、画面に表示しました。今回は、CO濃度の推移をチャートで見れるようにするため、測定値をクラウドに蓄積して可視化してみました。

CO2濃度の測定画面

クラウドサービス

データを蓄積するクラウドサービスはAmbientを使いました。無料枠でも、8チャネル×8種類のデータ、データの保存期間1年間と、個人で使うには十分すぎるサービスです。有料版は、データの受信監視や通知が利用できるようです。

クラウドサービスなので、M5StickC Plusからデータを送信するには、インターネットに繋がるWiFiのアクセスポイントが必要です。

セットアップ

Ambient公式ドキュメントの以下の手順で、チャネル生成まで行います。

  1. ユーザー登録(無料)
  2. チャネル生成

生成したチャネルの「チャネル設定」で、以下の通りデータ名を変更しておきます。

  • データー1:CO2
  • データー2:温度

Ambientのライブラリは、Arduino IDEのメニューバーの「ツール → ライブラリを管理…」で、「ambient」と検索すると見つかります。ライブラリ名は「Ambient ESP32 ESP8266 lib」です。バージョンは1.0.1でした。

ソースコードの更新

前回の記事のソースコードに、以下のコメント部分を追記します。「…」の部分は各自の環境に合わせて、WiFiのアクセスポイントの情報と、生成したチャネルのチャネルIDとライトキーを記載します。

#include <M5StickCPlus.h>
#include <MHZ19_uart.h>
// Ambientライブラリをインクルードする。
#include "Ambient.h"
const int rx_pin = 36;
const int tx_pin = 26;
// WiFiアクセスポイントのSSIDとパスワードを設定する。
const char* ssid = "...";
const char* password = "...";
// Ambientで作ったチャネルのチャネルIDとライトキーを設定する。
unsigned int channelId = ...;
const char* writeKey = "...";
MHZ19_uart mhz19;
// WiFiとAmbientの変数を宣言する。
WiFiClient client;
Ambient ambient;
void setup()
{
M5.begin();
pinMode(36, INPUT);
gpio_pulldown_dis(GPIO_NUM_25);
gpio_pullup_dis(GPIO_NUM_25);
Serial.begin(9600);
mhz19.begin(rx_pin, tx_pin);
mhz19.setAutoCalibration(false);
M5.Lcd.setRotation(3);
M5.Lcd.setTextSize(4);
// Wi-Fiアクセスポイントに接続する。
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(100);
}
M5.Lcd.println("WiFi connected.");
// Ambientを初期化する。
ambient.begin(channelId, writeKey, &client);
M5.Lcd.println("MH-Z19 is warming up now.");
delay(10 * 1000);
}
void loop()
{
int co2ppm = mhz19.getCO2PPM();
int temp = mhz19.getTemperature();
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setCursor(0, 0);
M5.Lcd.print("co2: ");
M5.Lcd.println(co2ppm);
M5.Lcd.print("temp: ");
M5.Lcd.println(temp);
// 測定値をAmbientに送信する。
ambient.set(1, co2ppm);
ambient.set(2, temp);
ambient.send();
delay(5000);
}

チャートの確認

Ambientでデータを送信したチャネルを開くと、CO2濃度の推移を可視化できました!
左のCO2のチャートを見ると、15:30:00過ぎに600ppmぐらいまで下がっています。これはエアコンをつけて窓を締め切った部屋から、ベランダに出たためです。

Ambientのチャート画面

回路図をdraw.ioで描いてWordPressで表示する方法

以下の記事でギターアンプの回路を設計しました。回路図はdraw.ioを使って描き、描いた図をWordPressで表示しました。その時の手順を紹介します。

回路図をdraw.ioで描く

draw.ioを開いたら「Create New Diagram」を選び、図のテンプレートを選びます。ここで「Engineering → Electrical 1」を選ぶと、抵抗やコンデンサ、オペアンプやトランジスタの図が左サイドバーに表示されるので、すぐに回路図を描き始められます。

draw.ioの画面

図の描き方はここでは説明しません。わりと直感的に描けるので、そんなに困らないと思います。

回路図をWordPressで表示する

以下の手順で、draw.ioで作成した図をWordPressで表示できます。手順はdraw.io公式の記事にある通りです。

  1. 右クリックして「Select All」を選び、右サイドバーの「Text → Formatted Text」のチェックを外す。
  2. 右クリックして「Select Vertices」を選び、右サイドバーの「Text → Word Wrap」のチェックを外す。
  3. 「File → Embed → SVG…」を選ぶ。
  4. 「Lightbox」のチェックを外して、「Embed」を押し、「Copy」を押してSVGのコードをコピーする。
  5. コピーしたSVGをWordPressにカスタムHTMLとして貼る。

以下のように回路図が表示できます。

4.7kΩ220μF100Ω10μF10μF9V0.047μF220μF1kΩB100ΩB8Ω0.5WLM386N-1Blue LED154236876.3mmStereo Jack