GolangでTello eduを遊んでみた?

この記事はOUCC Advent Calendar 2022の24日目の記事です。前回の記事はカキのたねさんの「c#でAWSLambda関数を作ってみた」でした。

今回の記事のタイトルは「GolangでTello eduを遊んでみた?」です。
なぜ?がついているのでしょうか。考えてみてください。

The Go gopher was designed by Renee French

はじめまして、Namifuji です。今日は家庭でも簡単に楽しむことができるドローンtello eduについてご紹介させていただきます。

まず、tello eduとはなんぞ?

Tello、Tello eduはドローン界のAppleと言われるDJIと、CPU部分をIntelで共同開発し、Ryze Techから販売されているトイドローンです。機能的にも立派にドローンです。
https://www.ryzerobotics.com/
上記のURLから約1万〜2万円で購入することができます。
そのため、皆さんのクリスマスプレゼントには持ってこいの品だと思います。
法律の許す範囲内で楽しんでいただけたらなと思います。
またTelloはスマホのアプリもしくはGolang、Python、Scrachを通じて操作することができます。
さらに取得した飛行風景をVRや、スマホ、コンピュータを用いることで手軽に見ることができます。
他にも顔識別や音声認識の機能も追加することができるようです。

環境

OS: macOS Ventura 13.0.0
Goのバージョン: go1.16 darwin/arm64
Pythonのバージョン:Python 3.10.9

IDE: Goland
エディタ: visual studio code

準備

Golangのフレームワークであるgobotをインストール

$ go get -d -u gobot.io/x/gobot/...

やったこと

1、ドローン自体の動作確認
スマホ版のアプリからドローンに接続し、動作確認

結果:飛んだ 。
<br/>
ここで先に以下で用いるドローンの飛行写真はアプリやGolangなどで飛ばした場合とすべて共通で用います。理由としましてはドローンを飛ばすときに発生する音があまりにも大きくて、近所迷惑にならないか不安で、撮る余裕があまりありませんでした。
<br/>
<br/>
<a href="https://blog.oucc.org/?attachment_id=993&quot; rel="attachment wp-att-993"><img src="https://blog.oucc.org/wp-content/uploads/2022/12/IMG_0293-1-2.jpg&quot; alt="" width="195" height="146" class="aligncenter size-full wp-image-993" /></a>

<br/>
<br/>

2、 gobotのフレームワークを使わずに、飛ばす。

package main

import "net"

func main() {
    conn, _ := net.Dial("udp", "192.168.10.1:8889")
    conn.Write([]byte("command"))
    conn.Write([]byte("takeoff"))
    conn.Write([]byte("flipoff"))
}

<br/>
<a href="https://blog.oucc.org/?attachment_id=993&quot; rel="attachment wp-att-993"><img src="https://blog.oucc.org/wp-content/uploads/2022/12/IMG_0293-1-2.jpg&quot; alt="" width="195" height="146" class="aligncenter size-full wp-image-993" /></a>
<br/>
結果:飛んだ 。
<br/>

<br/>

  1. gobotを使って飛ばす。
package main

import (
        "fmt"
        "time"

        "gobot.io/x/gobot"
        "gobot.io/x/gobot/platforms/dji/tello"
)

func main() {
        drone := tello.NewDriver("8888")

        work := func() {                                // この関数内に実行する動作を設定
                drone.TakeOff()

                gobot.After(5*time.Second, func() {
                        drone.Land()
                })
        }

        robot := gobot.NewRobot("tello",
                []gobot.Connection{},
                []gobot.Device{drone},
                work,
        )

        robot.Start()
}

<br/>
<a href="https://blog.oucc.org/?attachment_id=993&quot; rel="attachment wp-att-993"><img src="https://blog.oucc.org/wp-content/uploads/2022/12/IMG_0293-1-2.jpg&quot; alt="" width="195" height="146" class="aligncenter size-full wp-image-993" /></a>
<br/>
結果:成功。
<br/>
<br/>

4 pythonでも飛ぶの確認

import socket
import time

socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
tello_address = ('192.168.10.1' , 8889)

socket.sendto('command'.encode('utf-8'),tello_address)
socket.sendto('takeoff'.encode('utf-8'),tello_address)

結果:成功

上記で用いたコードから分かるように、ドローンを飛ばすためのプログラム自体はとても簡単に書けます。
接続先のipアドレスとポートを設定し、コマンドを送信するだけで遊べます。
Golang自体も簡単なプログラムを書く程度であれば、他の言語で何か作った経験があればすぐに理解できると思います。

上記の例ではただ単にドローンを離陸させただけです。

5 それ以外にしたこと
そこでドローンを空中で回転させたり、ドローンの高度を設定できるようにします。以下のgobotのドキュメントの参考にしました。(https://pkg.go.dev/gobot.io/x/gobot/platforms/dji/tello)
それによって入力したキーに応じて空中で一回転したり、時間と共に高度を上げたり、下げたりすることができました。

package main

import (
    "gobot.io/x/gobot"
    "gobot.io/x/gobot/platforms/dji/tello"
    "gobot.io/x/gobot/platforms/keyboard"
)

func handleKeyboardInput(drone *tello.Driver) func(interface{}) {
    return func(data interface{}) {
        key := data.(keyboard.KeyEvent).Key

        switch key {
        case keyboard.A:              // 左へ移動
            drone.Left(3)
        case keyboard.D:              // 右へ移動
            drone.Right(3)
        case keyboard.U:              // 前方へ移動
            drone.Forward(3)
        case keyboard.J:              // 後方へ移動
            drone.Backward(3)
        case keyboard.L:              // 着陸
            drone.Land()
        case keyboard.E:              // 終了
            return
        case keyboard. B:             // 後ろ向きに回転
            drone.BackFlip()
        default:                           // 移動しない
            drone.Forward(0)
            drone.Backward(0)
            drone.Left(0)
            drone.Right(0)
        }
    }
}

func main() {
    keys := keyboard.NewDriver()
    drone := tello.NewDriver("8889")

    work := func() {
        drone.TakeOff()
        drone.BackFlip()

        for {
            keys.On(keyboard.Key, handleKeyboardInput(drone))
        }
    }

    robot := gobot.NewRobot("tello",
        []gobot.Connection{},
        []gobot.Device{drone},
        work,
    )

    robot.Start()
}

終わりに

当日になって始めたので、とても中身の薄い内容となってしまいました。
そのため、春休みなど時間があるときに画像認識の機能などの機能もつけてみたいと思った。

"GolangでTello eduを遊んでみた?" の続きを読む

お気に入りの量産型・地雷系ファッションブランドについて語る

 この記事はOUCC Advent Calendar 2022の22日目の記事です。前回の記事はMedjedさんの「あなたの継承大丈夫?委譲で書き換えた方がよくない?」でした。
 皆様初めまして。OUCC2回生の紅茶です。ciffeliaさんの記事はもちろん、他の部員の記事のクオリティの高さに圧倒されるばかりです。watamarioさんの「OUCC の 3 年間を振り返る [前編]」を見て、技術系だけでなく文化的な話をするのもありだと思ったので(私に技術力がないので)趣味の話をします。

 実はここ1年ほどで「量産型・地雷系ファッション」にハマりました。

 量産型とはすとぷりやあんスタなどの若い男性アイドルのライブ会場によくいる「ザ・女の子!」みたいな恰好のことで、地雷系とは「明日、私は誰かのカノジョ」に出てくるホスト狂いの女の子「ゆあてゃ」みたいなザ・女の子だけどピアスバチバチで黒の多いどこか病んだ印象の恰好のことです。

 今は流行が多少違いますが違いがわかりやすいツイートがあったので載せておきます。
 地雷系と量産型の違い

 量産型・地雷系ファッションで知らない人はいないインフルエンサー・イモちゃんが有名どころのブランドをまとめた記事があるので紹介しておきます。これで大体は網羅できています。
 2020年 最新版!おすすめの地雷女子向けブランド
 【2021年最新版】おすすめの量産型ヲタク向け洋服ブランドまとめ

 これだけ大量のブランドがあると「どこから探せばいいのか分からない!」となる方もいらっしゃると思います。(OUCCに量産地雷服を着る人が私以外いるのかはツッコんではいけない)そこで私が個人的におすすめするブランドを書き残そうと思います。
 
おすすめブランド一覧は、安い順(最後のみ例外)に

  1. MA*RS
  2. LIZ LISA
  3. Secret Honey
  4. BUBBLES

の5つです。名前をクリックしていただければ公式通販サイトに飛べます。

 最初にMARSを紹介します。
 昔はギャル系ブランドとして有名だったのですが、時代の流れもあって次第に地雷系を押し出すようになりました。
このブランドで特徴的なのは

  • プチプラ価格で手に入る服
  • トップスの胸元の取り外し可能なサテンリボン
  • 界隈で有名なインフルエンサーとのコラボ

です。

一例を挙げると、肩開きクロスブラウス
 肩開きクロスブラウス
(画像は公式通販サイトより引用、これより下に登場する画像も同様)

 まず値段が4950円と量産型・地雷系ファッションブランドの中では安いです。ユニクロやGUに比べたら高いですが、後述のようにトップスで10000円弱は行くのが普通の世界なので「安い」です。
 安い分他のブランドに比べてデザインや質感で見劣りする部分もありますが(特にボトムスで顕著)、地雷系デビューにはもってこいです。
 もっと安い服で言えばSHEINとかに有名な量産型・地雷系ブランドをパクった服が1000円とかで売られていますが、クオリティは悲惨そのものです。
 また画像を見ていただいたら分かるように、胸元にサテンリボンがついていますよね。これはMARSならではです。
 そしてこのモデルさんは「砂糖みう」と言い、界隈では割と有名な方です。

次にLIZ LISAを紹介します。
 MARS同様昔はギャルブランドとして有名でしたが、今は量産型寄りのブランドになりました。
このブランドで特徴的なのは

  • リボンとビジュー、フリルをふんだんに使った服
  • とことん甘い、ガーリーな世界観
  • クオリティの高い小物類

    です。

 界隈で非常に知名度の高い服があるので紹介しておきます。セーラーカラーセットアップ
セーラーカラーセットアップ
 画像を見ていただいたら分かるように、リボンとビジュー、フリルが目立つデザインになっていますよね。実は後ろもリボンになっていて、結んでウエストアップできる優れものです。値段は11880円と跳ね上がりましたが上下セットの中ではまだ安いほうです。
 モデルさんもフランス人形を思わせるような儚く美しい雰囲気で、服の女の子らしさを前面に打ち出したデザインも相まって本当に人形がたたずんでいるようです。
 
小物類も同様で、リボンとビジューをふんだんに使ったデザインになっています。
ジュエリー4つセットリボンクリップ
ジュエリー4つセットリボンクリップ
 他のブランドの小物類が「正直100均で材料買って工作すれば作れるよね…」というものが多い中、リズリサは並大抵ではないこだわりを感じます。デザインが圧倒的に優れていると思いますし、大きさや色も豊富で見ていて楽しいです。

次にSecret Honeyを紹介します。
昔はマリンテイストの服やディズニーコラボの服を販売していました。特にディズニーコラボの服はクオリティが高く、転売が大量に発生したこともあったそうです。
このブランドで特徴的なのは

  • ウエストの引き締まった、女性らしさと可愛さを両立させたクオリティの高いセットアップ
  • レトロだけどトレンドを追うガーリーなデザイン

です。
アクセサリーリボン長袖セットアップ
スタンドフリルラッフルセットアップ
アクセサリーリボン長袖セットアップ
スタンドフリルラッフルセットアップ
 Secret Honeyのセットアップは界隈では非常に有名で、種類によっては販売開始から数日で店舗から在庫がなくなることもあります。
 フリルやリボンといった女の子が憧れる要素をたっぷり詰め込んだデザインだけでなく、品質のいい布、後ろのウエストリボンやウエストがきゅっと引き締まる服の作り、程よい脚の露出によってスタイルアップ効果も抜群といった要素もあって絶大な支持を得ています。
 HEP FIVEに行ったらSecret Honeyのセットアップを着た女の子にたくさん出会えるので幸せになります。
 LIZ LISAでも「リボンとフリルをふんだんに使ったデザイン」と出てきましたが、また違った雰囲気に仕上がっていますよね。それはこのSecret Honeyのコンセプト「レトロガーリー」にあります。LIZ LISAは女の子らしさを全面的に打ち出したかわいらしいデザインになっているのに対して、Secret Honeyでは少し大人っぽい落ち着いたデザインになっています。ここにも各ブランドのデザイナーさんのこだわりを感じますね。
 その分お値段は張って、上下セットで15000円とか恐ろしい値段になりますが買う価値は絶対にあります。自分を大好きになれる素敵な服です。

 最後にBUBBLESを紹介します。
BUBBLESは量産型・地雷系ブランドとしても有名ですが、圧倒的に有名なのは黒エナメルの厚底シューズです。界隈ではたいていの人がBUBBLESの厚底を持っています。
ダブルバックル厚底シューズ
ダブルバックル厚底シューズ
 見てくださいこの厚底!!!なんとつま先で6cm、かかとで13cmあります。値段も7590円と可愛くないですが、圧倒的に可愛いですし、スタイルを盛れますし、可愛さ最強の自分になれます。「背が高くて履けない…」や「厚底初心者だからもっと低いのがいい」と言う方に向けて、かかとの高さが9.5cmや5cmの同デザインのものも出ているのでご安心ください。
 他にも様々なデザインの黒厚底シューズが出ているのでぜひサイトをのぞいてみてください。

以上、個人的お気に入りブランド紹介を終わります。
皆様の量産型・地雷系ライフが良くなることを祈ります。
最後まで見てくださりありがとうございました!

Discordの読み上げBotを作っている話

この記事は OUCC Advent Calendar 2022 の 19 日目の記事です。昨日の記事は yuyu さんの「StackEditで非プログラマーと協同開発した話」でした。

はじめまして、ciffelia です。今日は私が開発している Discord 読み上げ Bot「Koe」と Koe で採用している技術をご紹介させていただきます。

Koeのロゴ
https://github.com/ciffelia/koe

読み上げ Bot って何?

Discord のテキストチャンネルに送信されたメッセージをボイスチャンネルで読み上げてくれる Bot です。
聞き専の人たちが送信したメッセージを合成音声で代読し、会話に参加しやすくしてくれます。

Koeが動作する様子を写したスクリーンショット

特徴

  • VOICEVOX を使用した高品質な音声合成
    • ずんだもん、四国めたんを含む 10 人以上の音源から好みに合ったものを選択可能
  • 特定の語句の読み方を設定する辞書機能
    • 読み方が難しいユーザー名の人がいても安心
  • ネタバレ(||で囲むやつ)は読み上げをスキップ

読み上げの仕組み

ボイスチャットに参加した Koe は概ね以下の手順でテキストを読み上げています。

  1. Discord API からメッセージを受信
  2. Redis から辞書データを取得
  3. 読み上げるテキストを生成
  4. Redis から声の設定(プリセット ID)を取得
  5. VOICEVOX Engine にテキストとプリセット ID を送信
  6. VOICEVOX Engine から合成音声の wav データを受信
  7. ffmpeg で音声データを変換
  8. 音声データを Opus にエンコードし、Discord に送信

使っている技術

プログラミング言語: Rust

Koe は Rust で書かれています。Rust は高速かつメモリ安全なプログラミング言語で、以下のような特徴があります。

  • 機械語にコンパイルされる
    • C や C++ と同じコンパイラ言語です。
    • Python や JavaScript のようなインタプリタ言語と比較して非常に高速に動作します。
    • ただしコンパイルは比較的遅いです。
  • GC が存在しない
    • 後述するように、変数の生存期間がコンパイル時に確定するためガベージコレクタが存在しません。
    • GC がメモリ管理を行っている Go と比較して高速に動作します。
    • Discord が Go から Rust に移行してパフォーマンスを改善した話が面白いです: Why Discord is switching from Go to Rust
  • 型システムがメモリ安全性を保証してくれる
    • 変数がコードのどの部分で使われているのかコンパイラがチェックします。
    • メモリ安全であることが保証されないとコンパイルが通りません。
    • Rust の学習が難しいと言われる所以はこのメモリ安全性チェックにあります。
    • use-after-free をはじめとする C/C++ で起こりがちなバグを未然に防ぐことができます。
    • 他の言語に類を見ない特徴的な機能です。
  • 標準のパッケージマネージャがある
    • Cargo と呼ばれるパッケージマネージャが同梱されています。
    • Cargo ではパッケージのことを crate と呼び、crates.io で検索できます。
    • Node.js の npm と同じような使い心地です。
    • npm ほど酷くはありませんが、Depenency Hell に陥ることもあります。
  • 高機能で書きやすい
    • 開発が活発なため、便利な機能が他の言語から多数取り入れられています。
    • 例えば、関数型言語のように、配列にmapfilterを適用できます。
    • JavaScript や C#のように、async / awaitで非同期処理を行うこともできます。
    • シンプルさを重視し限られた機能のみを実装する Go とは対照的です。どちらが良いか好みが分かれる部分かもしれません。

Discord API: serenity / songbird

Rust で Discord API を扱うための crate として最も有名なのが serenity です。基本的な API 呼び出し機能に加えてキャッシュ機構を備えており、Discord API の扱いにくい部分を隠蔽してくれます。
また、Discord のボイスチャットに関する API を扱うための crate が songbird です。ボイスチャンネルに Bot として接続し音声を流す機能を備えています。

音声合成: VOICEVOX Engine

読み上げソフトウェアとして有名な VOICEVOX から音声合成エンジンを切り出したものが VOICEVOX Engine です。
VOICEVOX Engine に対して HTTP リクエストでパラメータを送ると、合成された音声データを返してくれます。

データベース: Redis

Redis は key-value store として使えるデータベースです。いわゆる NoSQL データベースの一つです。
Koe では辞書やユーザーの声などの設定を保存するために使用しています。

コンテナ管理: Docker

Docker は言わずと知れたコンテナ管理ソフトウェアです。コンテナとは、超軽量な Linux の仮想マシンのようなものなのですが、簡潔かつ正確に語るのは難しいので詳細は割愛します。
Koe ではdocker-compose.ymlを配布しており、コマンド一つで簡単に Koe、Redis、VOICEVOX Engine のコンテナを起動・停止できるようにしています。

リリース自動化: GitHub Actions

GitHub Actions は、GitHub の管理する仮想マシン上で様々なイベントをトリガーとして任意の処理を実行できるサービスです。
Koe では以下のような処理を自動化しています。

  • リポジトリにコードがプッシュされたとき
    • コンパイルが通るかチェック
    • コンテナをビルドして GitHub Container Registry にプッシュ
  • 私がリリースボタンを押したとき
    • 新しいバージョンをリリースするコミットを作成
    • バージョンに対応する Git のタグを作成
    • バージョンに対応する Docker コンテナのタグを作成
    • リリースノートを作成

依存しているソフトウェアのアップデート自動化: Renovate

Koe は様々なソフトウェアに依存しています。先述した serenity, songbird, Redis に加えて、設定ファイルのデシリアライザとして Serde、HTTP クライアントとして reqwest などを利用しています。
それらの新しいバージョンがリリースされるたびに手動で更新を行うのは面倒なので、アップデートを自動化する Renovate を導入しました。Renovate は指定した時間に自動で Pull Request を作成し、ビルドが通るとマージしてくれます。

おわりに

Koe を使ってみたくなった方はドキュメントを読めば簡単に導入できます。ソースコードが公開されているため改造も容易です。ぜひお試しください。
また、今回ご紹介した技術の多くは Discord Bot の開発以外にも活用できるものです。これらの技術を活用し、効率的なソフトウェア開発に役立てていただければ幸いです。

StackEditで非プログラマーと協同開発した話

執筆者:yuyu

この記事は OUCC Advent Calendar 2022 の18日目の記事です。前回はみやじさんによる「ASP.NET Core を使ってREST APIを作ってみる」でした。

最近の良かったことはサッカーのワールドカップで日本が出た全試合をリアタイし、サッカーに少し詳しくなれたことです。

もともとは「オフサイドってなんやろ」「4-4-2とは?」というレベルの知識で、選手に関しても長友選手や AbemaTV で解説を務めた本田圭佑などの時代しか知りませんでした。解説や Twitter のつぶやきを理解するためにルールや戦略論(フォーメーションなど)を調べていきサッカーの奥深さに少し気づけたかなと思います。

さて、今回の記事では、今年の春に非プログラマーと協同で web ページの作成を担当し、リーダーとして動いていた経験をもとに、当時意識していたことを書きたいと思います。

目次

  1. はじめに:背景
  2. 問題点
  3. 解決策
  4. 導入:開発裏話
  5. 結果
  6. おわりに
  7. リンク

1. はじめに:背景

私は別の団体と兼部をしているのですが(以下、G隊)、G隊では春の学祭にてクイズを使った出し物をしようということになりました。教室の中で完結するのですが、スタンプラリーのように順番に数問のクイズを解いていく形式で、最初の頃は設問を印刷して来場者に自分のペースで順番に解いてもらおうという議論でした。しかし、「様々な種類のクイズを用意したい」「答えは別用紙で用意する必要がある」「感染予防のため同じ紙を何枚もすらなければならない」などの理由から紙媒体ではなく電子媒体を用い、例えばQRコードを読み取ってもらってリンク先の web ページでクイズを読めるようにしようということになりました。結果的に印刷費用を抑えられるという利点もありました。

2. 問題点

非常にスマートで費用もかからないので現代の価値観に合った素晴らしい企画に思われましたが、ひとつだけ問題がありました。それは web ページを作ることができる人材の不足でした。

当時は私一人が G 隊の web 開発を担当していました。クイズ作成は複数人のチームで分担して行っていましたが、それを web ページにする仕事はひとりでやるには業務量が多すぎました。ぜひ web ページ作成も分担したいところですが、G 隊には web プログラマーは私一人しかいなかったので HTML & CSS で対応することは困難でした。

3. 解決策

HTML & CSS が使えないので Markdown 記法を用いて書けるシステムを採用すべきだと考えました。

Markdown(マークダウン)は、文章の書き方です。デジタル文書を活用する方法として考案されました。特徴は、

・手軽に文章構造を明示できること

・簡単で、覚えやすいこと

・読み書きに特別なアプリを必要としないこと

・それでいて、対応アプリを使えば快適に読み書きできること

などです。Markdownはジョン・グルーバー(John Gruber)によって2004年に開発され、最初は Daring Fireball: Markdown で公開されました。その後、多くの開発者の手を経ながら発展してきました。

Markdown とは(日本 Markdown ユーザー会)

Markdown 記法であれば少なくとも HTML & CSS よりは非プログラマーにも簡単に web ページを作ることができます。

では、Markdown 記法を使うとして、どんなツールを使えばよいのでしょうか。条件をいくつか出しました。

  • 共有しやすい(Git Hub 以外の方法で)
  • html ファイルに変換できる(サーバーをレンタルしていなかったため、GithubPages を使う必要があった)
  • 無料のサービスである

結果、"StackEdit"なるサービスを発見しました。これはブラウザ上で Markdown 記法を使って web ページを作ることができるサービスで、アプリのダウンロードが不要だったこともありがたく感じました。

StackEdit の素晴らしい点は Google Drive と連携できる点でした。G 隊はファイルの共有などは Google Drive で行っていたので今まで通りに各自で作成した web ページを Google Drive で共有できるというのは魅力的でした。進捗管理にも大変役立ちました。

また、html ファイルにエクスポートすることもできました。G 隊はサーバーをレンタルしておらず、web ページを持つために私の GitHub アカウントで GithubPages を作成して公開していました。GithubPages には html ファイルと css ファイルしか使えないという先入観がありました( md ファイルでもアップロードできたのかもしれないがよくわからん)。

StackEdit のエディター画面

デザインは凝ったものにできませんが、学祭でしか使わないページになるだろうということで妥協することにしました。

こうして、学祭担当のチームのみんなには StackEdit を使ってクイズのページを作成してもらい、Google Drive に共有されたファイルを私が html ファイルに変換して GitHub のリポジトリにアップロードするという体制ができあがりました。

4. 導入:開発裏話

ここまでは企画運営の方針に関しての話でしたが、ここからはチームのみんなに環境構築をしてもらう段階となります。導入時に参照するドキュメントを作成し、Google Drive 上に共有しました。

導入用ドキュメント

意識したことは IT 用語(プログラミングの用語や web 開発の文脈の用語)を使わないよう徹底したことです。自分の知らない言葉で埋め尽くされたドキュメントは読む気にならないでしょう。例えば英語の文章において98%以上の語彙を理解して初めて適切な読解が可能になると言われています(Hu & Nation, 2000; Laufer, 2010; Schmitt, Jiang & Grabe, 2011)。知らない語彙が一定数あると読解精度が落ちるわけですが、個人的にはこの現象は外国語の文章に限らず専門的な内容の文章を読む場合でも変わらないと思っています。

ドキュメントの整備に加えて、質問しやすいような環境づくりも意識しました。定例会議では何か困ったことがないか尋ね、相談してもらえるようにしました。

準備が整ったら各自好きなタイミングでクイズを書き、Google Drive にアップロードしてもらいました。それを私がチェックするという流れです。

5. 結果

クイズ画面
クイズ画面

クイズ画面は上のような感じになりました。スッキリとした印象で、問題を解くうえではかなり見やすい画面になっているように思います。プログラミングを使わなくてもここまでできるのは素敵ですね。

学祭当日は、私は忙しかったので運営として参加することはできませんでしたが盛況だったと聞いています。貢献できたようで嬉しい限りです。

また、この StackEdit 導入をきっかけに G 隊内において web 開発を始めてみることへの心理的抵抗感が弱まり、チームでの web 開発につなげやすくなったことは思わぬ幸運でした。

6. おわりに

新しくプログラミングに興味を持ってもらうには、「なんだか難しそう」という食わず嫌いを払拭させ、自分にもできそうだと思ってもらうことが何よりも重要でした。これはプログラミングに限らず新たな技術を導入する時には常に心掛けるべきことでしょう。チームの抵抗感を和らげ、スムーズな導入を目指しましょう。

OUCC Advent Calendar 2022 、次回の担当は ciffelia さんです!お楽しみに!

7. リンク

StackEdit:https://stackedit.io/

ASP.NET Core を使ってREST APIを作ってみる

ASP.NET Core を使ってREST APIを作ってみる

執筆者:みやじ

これは OUCC Advent Calendar 2022 の17日目の記事です。
ここではTodoアプリのバックエンドを想定して簡単な REST APIを作ってみようと思います

目次

プロジェクトを作る

Visual Studioで ASP.NET Core Web API のプロジェクトテンプレートを選択して作成します。
実行するとhttps://localhost:<port>/swagger/index.htmlが自動的に開き最初からある WeatherForecast API が表示されます。ここで<port>ランダムに選択されたポート番号です。よく見るSwaggerページだと思います。Try it out で試しに実行することもできます。

サンプルデータ用のサービスを作成する

まず次のようなTodoItemクラスを作成します。

public class TodoItem
{
    public int Id { get; set; }
    public string? Title { get; set; }
    public string? Description { get; set; }
    public DateTime DueDate { get; set; }
}

つぎにそれを扱うITodoItemContainerを作ります。実装は少し長くなったので下の方に書いておきます。単純にList<TodoItem>を用意してそれに足したり抜いたりしているだけです。

public interface ITodoItemContainer
{
    IEnumerable<TodoItem> GetTodoItems();
    TodoItem? AddTodoItem(TodoItem item);
    bool RemoveTodoItem(int id);
    bool UpdateTodoItem(TodoItem item);
}

ASP.NET Core は依存性注入(DI)を採用しているのでITodoItemContainerを API Controller で使うためにはDIコンテナに登録する必要があります。 Program.cs でbuilder.Build();が呼び出されている箇所より上でbuilder.Services.AddSingletonを呼び出せば登録することができます。
今回は次のコードをを追加しました。

builder.Services.AddSingleton<ITodoItemContainer, TodoItemContainer>();

API コントローラーの作成

API コントローラーはControllerBaseを継承して作ります。これを継承することで応答に便利な関数を利用できます。

[ApiController]
public class TodoItemsController : ControllerBase

ApiCotroller属性をつけると以下のようなAPIの動作を有効化できます。

  • 属性ルーティング要件
  • 自動的な HTTP 400 応答
  • バインディング ソース パラメーター推論
  • マルチパート/フォーム データ要求の推論
  • エラー状態コードに関する問題の詳細

以下のように API コントローラーにメソッドを定義することでアクションを設定できます。ApiController属性を付けているとHttpGet属性なしでもメソッド名にGET POST PUT DELETEが含まれていると自動的にHTTPメソッドとして登録されますが、Swaggerが認識できないのでHttpGet属性をつけるほうが良いです。

[HttpGet]
public ActionResult<TodoItem> GetTodo() { }

ルーティング

ルーティングはRoute属性を用いてコントローラーまたはアクションに設定できます。ASP.NET Core のルーティングは大文字小文字の区別がありません。api/todoでもApi/Todoでも同じものとして扱われます。
コントローラにRoute属性を使うとそのコントローラーでのデフォルトのルートが設定されます。[controller]はコントローラーの名前から自動的に Controller の部分を取り除いた文字列が入れられます。
アクション、つまりメソッドにRoute属性を使うとそのメソッドだけにルーティングを設定できます。このとき、/または~/で始めるとコントローラーのルートテンプレートと結合されません。

また HttpGet などの HTTP 動詞テンプレートでもルーティングは設定できます。 HTTP 動詞テンプレートを使うとその HTTP 動詞だけを受け付けるように制限されるので REST API を作るなら全て HTTP 動詞テンプレートを使うこと方が良いです。 HTTP 動詞テンプレートは以下の6つです。

  • [HttpGet]
  • [HttpPost]
  • [HttpPut]
  • [HttpDelete]
  • [HttpHead]
  • [HttpPatch]
[Route("api/[controller]")]
[ApiController]
public class TodoItemController : ControllerBase
{
    [HttpGet]          // api/TodoItem
    public ActionResult<TodoItem> GetTodo() { } 

    [HttpGet]
    [Route("todo")]    // api/TodoItem/todo
    public ActionResult<TodoItem> GetTodo() { }

    [HttpGet("todo")]  // api/TodoItem/todo
    public ActionResult<TodoItem> GetTodo() { }

    [HttpGet("/todo")] // todo
    public ActionResult<TodoItem> GetTodo() { }
}

メソッドの引数

メソッドの引数はURLパスから受け取ったりリクエスト本文から受け取ったりできます。

URLパスから情報を受け取る

Route属性やHttpGet属性などで{id}のように書くことで、引数idにその値を受け取ることができます。また、{id?}とすることで省略することができます。省略された場合引数にはデフォルトの値が入ります。

[Route("api/[controller]")]
[ApiController]
public class TodoItemController : ControllerBase
{
    // api/TodoItem/1 => id = 1
    // api/TodoItem   => id = 0
    [HttpGet("{id?}")]
    public ActionResult<TodoItem> GetTodo(int id) { }
}

URLクエリパラメータやリクエスト本文から受け取る

それぞれ以下の属性を引数につけることでリクエストから受け取ることができます

  • FromQuery リクエストのクエリパラメーター
  • FromBody リクエスト本文
  • FromForm リクエスト本文内のフォームデータ
  • FromHeader リクエストヘッダー

FromBodyはSystem.Text.Jsonを使って自動的にパースしてくれます。

[Route("api/[controller]")]
[ApiController]
public class TodoItemController : ControllerBase
{
    [HttpPost]
    public ActionResult<TodoItem> CreateTodo([FromQuery] int id, [FromQuery] string title) { }

    [HttpPost]
    public ActionResult<TodoItem> CreateTodo([FromBody] TodoItem todo) { }
}

メソッドの返り値

メソッドの返り値にはActionResult<T>を指定しておけば暗黙的なT=>ActionResult<T>のキャストが実装されているのでとりあえずは困らにと思います。型が指定できないとき、例えば匿名型を使用するときなどはActionResultを使えばよいです。
そのまま値を返しても問題ありませんが、ControllerBaseに実装されているメソッドを利用することで一緒にStatusCodeを指定することもできます。よく使われそうなものをいかに列挙しておきます。

  • Ok(Object)
    HTTP 200 OK を返すオブジェクトを生成する。
    引数にはアクションの返り値にしたいオブジェクトを指定する。
  • Created(String, Object)
    HTTP 201 Created を返すオブジェクトを生成する。
    第1引数にはコンテンツが作成された URI を、第2引数にはアクションの返り値にしたいオブジェクトを指定する。
  • CreatedAtRoute(String, Object, Object)
    HTTP 201 Created を返すオブジェクトを生成する。
    第1引数には作成されたコンテンツを返すアクションの名前を、第2引数にはそのアクションのURLの生成に使用するルートデータ。第3引数にはアクションの返り値にしたいオブジェクトを指定する。
  • NotFound()
    HTTP 404 Not Found を返すオブジェクトを生成する。

もっと見る場合は ControllerBase クラスのドキュメントを参照してください。

[Route("api/[controller]")]
[ApiController]
public class TodoItemController : ControllerBase
{
    private ITodoItemContainer _todoItemContainer;

    public TodoItemController(ITodoItemContainer todoItemContainer) {
        _todoItemContainer = todoItemContainer;
    }

    [HttpGet("{id}")]
    public ActionResult<TodoItem> GetTodo(int id) {
        var todo = _todoItemContainer.GetTodoItems().FirstOrDefault(t => t.Id == id);
        return todo is null ? NotFound() : Ok(todo);
    }

    [HttpPost]
    public ActionResult<TodoItem> AddTodo([FromBody] TodoItem item) {
        var todo = _todoItemContainer.AddTodoItem(item);
        return todo is null ? BadRequest() : CreatedAtAction(nameof(GetTodo), new { Id = todo.Id }, todo);
    }
}

完成

他にPUT DELETEメソッドを追加して最終的に次のようになりました。

TodoItemControllerの実装

[Route("api/[controller]")]
[ApiController]
public class TodoItemController : ControllerBase
{
    private ITodoItemContainer _todoItemContainer;

    public TodoItemController(ITodoItemContainer todoItemContainer) {
        _todoItemContainer = todoItemContainer;
    }

    [HttpGet]
    public ActionResult<TodoItem[]> GetTodoList() {
        return Ok(_todoItemContainer.GetTodoItems().ToArray());
    }

    [HttpGet("{id}")]
    public ActionResult<TodoItem> GetTodo(int id) {
        var todo = _todoItemContainer.GetTodoItems().FirstOrDefault(t => t.Id == id);
        return todo is null ? NotFound() : Ok(todo);
    }

    [HttpPost]
    public ActionResult<TodoItem> AddTodo([FromBody] TodoItem item) {
        var todo = _todoItemContainer.AddTodoItem(item);
        return todo is null ? BadRequest() : CreatedAtAction(nameof(GetTodo), new { Id = todo.Id }, todo);
    }

    [HttpPut]
    public ActionResult<TodoItem> UpdateTodo([FromBody] TodoItem item) {
        var result = _todoItemContainer.UpdateTodoItem(item);
        return result ? Ok(item) : BadRequest();
    }

    [HttpDelete("{id}")]
    public ActionResult DeleteTodo(int id) {
        var result = _todoItemContainer.RemoveTodoItem(id);
        return result ? NoContent() : BadRequest();
    }
}

ITodoItemContainerの実装

public class TodoItemContainer : ITodoItemContainer
{
    private List<TodoItem> _items;

    public TodoItemContainer() {
        _items = Enumerable.Range(1, 10)
            .Select(i => new TodoItem {
                Id = i,
                Title = $"todo item id:{i}",
                DueDate = DateTime.Today.AddDays(1)
            }).ToList();
    }

    public IEnumerable<TodoItem> GetTodoItems() => _items;

    public TodoItem? AddTodoItem(TodoItem item) {
        if (item.Id >= 1 && _items.Any(t => t.Id == item.Id))
            return null;

        if (item.Id < 1)
            item.Id = _items.Max(t => t.Id) + 1;
        _items.Add(item);
        return item;
    }

    public bool RemoveTodoItem(int id) {
        var todo = _items.FirstOrDefault(t => t.Id == id);
        return todo is not null && _items.Remove(todo);
    }

    public bool UpdateTodoItem(TodoItem item) {
        var todo = _items.FirstOrDefault(t => t.Id == item.Id);
        if (todo is null)
            return false;

        _items[_items.IndexOf(todo)] = item;
        return true;
    }
}

あとがき

HTTP Status Code がどれを使えばいいのか全然わからなかったです。加えてうまくSwaggerに反映されてくれなくてよくわからなかったです...

参考

dotnet-scriptを使ってC#でも書き捨てプログラムを

執筆者: しおかい

これは OUCC Advent Calendar 2022の12日目の記事です。
本題まで長いので読み飛ばしていただいて結構です。

動機

大学の講義というものは、工学部・基礎工学部に限らずプログラムを書かされることは間々あります。
そういったときに、特に言語を指定されなければ自分の慣れている言語で書きたいものです。

ところで、筆者にとってその「慣れている言語」というのがC#なわけですが、これがどうにも講義で使うのには相性が良くないのです(個人の感想です)。

講義でプログラムを求められる状況では、うちの場合大抵その一回限りの使い捨ての、コンソールアプリとすら言わないような実行して出力を見るだけといったようなものを書くことになります。
C++やPythonであれば、(予め環境構築が終わっていれば)適当に1つファイルを作って書き捨て、実行すればそれでいいので何も思うところはありません。

ところがC#(.NET)では、一番簡単なコンソールアプリでも、専用にフォルダを用意し、dotnet new consoleしてプロジェクトを作らねばなりません。
しかも、プログラムが記述されたファイルと実行ファイル以外にいろいろファイルやフォルダが生成される始末で、「書き捨て」をするには余りに仰々しすぎるのです。

試行錯誤

もっと、それこそC++やPythonのように、プログラムが記述されたファイルと、せいぜいコンパイル後の実行ファイル程度でどうかしたい。
そう思った私は、「そもそもdotnet経由で実行しようとするからプロジェクトが要るのでは」と思い、csc.exe(CSharp Compiler)を直接叩いてコンパイルしようと思い立ちました。
実際これでコンパイル、実行自体はできるのですが、いくつか問題がありました。

インテリセンスが効かない

まず、始めにやってみて気が付いたのは、単に.csのファイルを開くだけでは、VSCodeでインテリセンス等が全くと言っていいほど効かないのです!
C#と言えば静的解析(ほんとに?)。
それが効かないのは余りに面倒でした。

ではcscを叩く方法でC#にインテリセンスを効かすにはどうするか。
いくつか方法はあるのですが、最も単純なのは、*.csprojをワークスペースに配置することです。
これはdotnet new consoleすれば簡単に生成できます。
結局dotnet new consoleが必要なものの、1回で済むならだいぶ状況は良くなったと、初めはそう思いました。

フォルダ内にエントリポイントがあるファイルを複数置けない

この方法の問題点は何か。それは、VSCodeで開いたフォルダ全体が1つのプロジェクトとして認識されるため、エントリポイントを1つしか持てないということです。
言い換えれば、Mainのあるファイルがフォルダ内に複数あると、VSCode上でエラー表示が出るのです。

もちろん実際にはcscでコンパイルするのでエラーは無視できるのですが、うっとうしいですし、本当にエラーになっているものを見逃すかもしれません。
適当にファイルの中身の切り貼りを自動化すれば、一応大した手間なくエラーも無くコンパイルもできる状態にはできるものの、各.csファイルは単体でコンパイル/実行不可能なうえ、自動化用の諸々(当時私は専用のC#プログラムとtask.jsonで行っていました)がフォルダ内に散乱して、結局当初の目的をあまり達成できていませんでした。

光明

さてそうこうしいたある時、「そういえばC#をインタープリタ的に実行できるのがあった気がする」と思い出しました。
それについて調べたところ、dotnet-scriptというものを見つけ、これがまさに自分の求めていたものでした。
[注釈1]:ところで、先ほど「いくつか方法はある」といったところにC# scripts (CSX)と書いてあり、これがそのdotnet-scriptのファイルなのですが、ここに書いてあったことにこれを書いている今気が付きました。
(OUCC BLOGってMarkdonwのfootnote記法使えないのか……)

というわけで、今回このC# scriptsについての紹介になります。
(ここまで前置き)

本題

インストール

C# scripts(dotnet-script)とはどういうものなのかの話の前に、簡単にインストール方法を説明しておきます。
といっても、.NET6か.NET7のSDKをインストールすれば、あとはコマンドラインからdotnet tool install -g dotnet-scriptをするだけです。

できること

dotnet-scriptでは以下のことができるようです

  • アプリケーションへの埋め込み
  • Interactive実行(これが先ほど言った「インタープリタ」的実行)
  • スクリプト実行(主目的)

このうちアプリケーションへの組み込みは今回説明しません。

Interactive実行

コマンドラインでdotnet scriptまたはdotnet-scriptと入力することで、インタプリンタ環境が起動します。
文法は通常のC#とほぼ同じです。
リテラルや変数を;無しで打つと値を表示してくれます。
またおそらくImplicitUsing(後述)によるものですが、初めからある程度using済みなので、少し変わった電卓程度の用途には十分使えます。

なお起動後初めの出力が異様に遅いので、適当に数字を打って出力させておくといいでしょう。

一応後述のスクリプトファイルを読み込んだり、NuGet参照したもできますが、あまり複雑なことをするには(当然ながら)向いてはいません。
せいぜい打ち間違いに強い電卓でしょう(個人のry)。

なお終了は#exitで行えます。

スクリプト実行

さて本題、求めていたスクリプト実行です。
VSCodeの場合、ワークスペースでdotnet script initとすれば、直下にomnisharp.jsonmain.csxが、.vscode下にlaunch.jsonが生成されます。

これができればあとは好きにC#を書いていけます。
新規にスクリプトファイルを生成するのはdotnet-script new filenameでできます。
(普通にファイルを作っても問題ありませんが、たまにスクリプトファイルとして認識してくれずインテリセンス等が効かなくなることがあります。その場合、VSCodeを開きなおせば問題ありません。)

注意点として、Mainを書くと、実行時に
warning CS7022: プログラムのエントリ ポイントは、グローバル コードです。エントリ ポイント 'test.Main(string[])' を無視します。と言われて読み飛ばされてしまうので、トップレベルステートメントの記法を使用しましょう。
また、同じワークスペースで拡張子が.csのファイルを開いてしまうとインテリセンス等が効かなくなる場合があるので、拡張子を間違えないようにしましょう。
スクリプトファイルの拡張子は.csxです。
万一インテリセンスが効かなくなっても、.csなファイルを閉じてからVSCodeを開きなおせば問題ありません。

またImplicitUsingと思われる暗黙的にusingされている名前空間もあります。
一覧を見つけられなかったので、VSCodeのオートコンプリートを使って、aからzまで一文字打っては補完候補を見ていくという頭の悪い手法で調べたところ、dotnet-scriptで暗黙的にusingされているのはおそらく以下の通りです。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using System.Text;
using System.Diagnostics;
using System.Dynamic;
using static System.Console;

コンソールアプリケーションとも少し違うようです。
System.Consoleusing staticされているおかげで、標準出力がWriteLine()で済むので楽です。

NuGet参照も使え、ファイル先頭に#r "nuget: PackageName, 1.0.0"のように書くことで参照できます。
ただし、これを追記した後はVSCodeを開きなおさないと、名前空間等をVSCodeが認識してくれないのが難点です。

また、実行はdotnet-script filepathで行えます。
VSCodeならdotnet script initしていればlounch.jsonが生成されているはずなのでF5でもいいでしょう。

一応コンパイル(発行)もdotnet-script publish filepathで行えます。
ただし何故かワークスペース直下にあるファイルをpublishできないので、コンパイルしたい場合は一段下の階層に入れましょう。

詳細な記法はGitHubを見るといいと思います(ほぼトップレベルステートメントと変わりませんが)。

トップレベルステートメントやImplicitUsingが分からない場合、アドベントカレンダー7日目の私の記事を見ていただければと思います(露骨な宣伝)。

終わりに

というわけで、dotnet-scriptによるスクリプト実行によって、C#で快適に書き捨てプログラムを書けるようになりました。
これで、「『ファイルを作ってハイ実行』って訳にはいかないから気軽にC#を使えない」と言ってC++やPythonを使っていた皆さんも、思う存分C#を書けますね!

pythonとArduinoと画像認識でタワーディフェンスゲームの自動化をしてみた

前書き

この記事はOUCC Advent Calendar 2022の8日の記事です。担当はMrMocchyです。
作成したプログラムはGithubに上げていますので全体をご覧になりたい方はこちらへどうぞ。
この記事ではゲームの自動化という内容を扱っています。これはゲーム開発者が想定していないだろう遊び方ですが、やっていることはプレイヤーの操作をプログラムに代替させているだけです。リソースを違法に爆増させるような類のチートではないのでお目こぼしください。
また、このゲームをやったことがないと何言ってるのか分からない部分も多いとは思いますが、そういうものだと思ってください。

自動化したゲームについて

自分はInfinitode 2というタワーディフェンスゲームを長いこと遊んでいるのですが、これはゲーム性として「プレイして得たリソースでタワーを強化し、次のプレイへ」という周回を繰り返す、盆栽ゲーと呼ばれることもあるタイプです。ステージごとにマップも敵の種類も大きく変わる、タワーと敵の相性により与えるダメージが0%~200%になる、無限の強化と無限の敵、etc...、とまあここはレビューサイトではないので切り上げますが、自分にとっては非常に奥の深いゲームだと思っているわけです。
しかしやりこんでいくとステージごとにある程度の最適解のようなものが見えてくるわけで、それを自動化に定評のあるらしいpythonを用いて放置ファームしようというわけです。

プログラム全体の流れ

上で書いた最適解、すなわちどこに何をするのか、というのをcsvに羅列してそれを順に行わせます。
csvファイルには[行動、座標、オプション](後ろ2つはあったりなかったり)を列挙しています。別の[行動、カテゴリ、ホットキー]を並べたhotkeys.csvから読み込んだ辞書型を参照して、[座標、カテゴリ、ホットキー]などを持つクラスのリストとして読み込みます。そして現在の所持金などからその行動をできるか判断して実行するというループでプレイさせます。
現在の所持金や設置・アップグレードのコストなどはPyOCRを使って画面から文字認識で読み取っています。

操作の自動化

Infinitode 2にはホットキー機能があり、キーボード入力一つでタワー設置やアップグレードからゲーム進行速度変更までできます。これをpyautoguiで操作しました。しかし、移動の矢印キーはホットキーで変更できず、pyautoguiが利きませんでした。調べた結果、自作キーボードなどに用いられるAtmega32U4というマイコンを積んだArduinoなら操作できるのではと考え、amazonでArduino Pro Microの互換機らしきものを購入しました。あとは

を参考に、pythonからArduinoにシリアル通信で文字を送り、それに応じたキーボード入力操作をさせました。

# control.py抜粋
import os
import serial
import time

#シリアル通信のセットアップ
ser = serial.Serial()
#デバイスマネージャでArduinoのポート確認
readlines = os.popen("powershell \"Get-CimInstance Win32_PnPEntity | Where-Object {$_ -like \'*Arduino*\'} | Select-Object Caption\"").readlines()
if readlines == []:
    print("error @ control")
    print("Arduino was not found")
    exit()
PORT = readlines[3][15:-2]#"COM5"の形のポートを取得
ser.port = PORT
ser.baudrate = 9600       #Arduinoと合わせる
ser.setDTR(False)         #DTRを常にLOWにしReset阻止
ser.open()
#終了時に ser.close() をする

def moveBy(dx,dy):
    string=b""
    if dx<0:
        string+=b"l"*(-dx)
    else:
        string+=b"r"*dx
    if dy<0:
        string+=b"d"*(-dy)
    else:
        string+=b"u"*dy
    ser.write(string)
    #ゲーム側のカーソル移動を待つ
    time.sleep(0.2*(abs(dx)+abs(dy)))
//move/move.ino
//Arduino側プログラム
#include "Keyboard.h"
void setup() {
  Serial.begin(9600);
  Keyboard.begin();
}
void loop() {
  if (Serial.available() > 0) {
    switch (Serial.read()) {
      case 'r':
        Keyboard.write(KEY_RIGHT_ARROW);
        break;
      //以下略
    }
  }
}

画像認識部分

などを参考に、ゲーム画面から現在の所持金やコストなどの文字の部分を切り取って文字認識で読み込みました。
ゲーム内で3:4のように表示された座標を読み取る際に特にうまく読み取れないことが多かったので、認識精度を上げるためにいろいろ試しました。その中で効果があったと思われるものを挙げます。

  • 切り取る画像の範囲を読み取りたい文字列いっぱいにまで狭める。
  • 黒字に白文字は誤認識しやすいようなので、で反転して二値化。(from PIL import Imageを使用)
  • 上の二値化ついでに画像を横に拡大して、細い半角の文字を細めの全角くらいにする。(数字の認識力は向上したが、コロンを誤認識することも増えたので結局使わなかった)
  • 切り取る部分によって二値化の閾値を調整
  • tesseract_layoutという読み取る方法(?)を変更する引数は、いろいろ試したけど結局6でよさげだった。

また、指定の画像を画面から見つけるのを用いて、設置コストの画面切り替えやポーズ画面の判定をしたりしています。

# ocr.py抜粋
def getImage(_left,_top,_right,_bottom):
    #ウィンドウが前面にあるか入力前にチェックし、なければカウントダウンして終了
    foregroundCheck()
    # ウィンドウサイズを取得し、ずれを調整
    f = ctypes.windll.dwmapi.DwmGetWindowAttribute
    rect = ctypes.wintypes.RECT()
    DWMWA_EXTENDED_FRAME_BOUNDS = 9
    f(ctypes.wintypes.HWND(hwnd),ctypes.wintypes.DWORD(DWMWA_EXTENDED_FRAME_BOUNDS),ctypes.byref(rect),ctypes.sizeof(rect))
    # 取得したサイズでスクリーンショットを撮る
    image = ImageGrab.grab((rect.left+2+_left, rect.top+31+_top, rect.left+2+_right, rect.top+31+_bottom))
    return image

def getTextFromImage(rect,border,show=False) -> str:
    img=getImage(rect[0],rect[1],rect[2],rect[3])
    if img == None: return
    img=img.convert("RGB")
    size=img.size
    img=img.resize((size[0]*2,size[1]*2))
    size=img.size
    img2=Image.new("RGB",(size[0],size[1]))
    #2値化して精度を上げる
    for x in range(size[0]):
        for y in range(size[1]):
            r,g,b=img.getpixel((x,y))
            if r+g+b > border*3:
                r,g,b=(0,0,0)
            else:
                r,g,b = (255,255,255)
            #img2.putpixel((x*2+1,y),(r,g,b))
            img2.putpixel((x,y),(r,g,b))
    txt = tool.image_to_string(img2,lang,builder=pyocr.builders.TextBuilder(tesseract_layout=6))
    #指定範囲の調整用に切り取った画像と二値化後の画像を並べて表示
    if __name__ == '__main__' or show:
        imgs=Image.new("RGB",(img.size[0]+img2.size[0],img.size[1]))
        imgs.paste(img,(0,0))
        imgs.paste(img2,(img.size[0],0))
        imgs.show()
    return txt

その他

csvファイルから読み込んだプレイ動作のアルゴリズムはAlgoという名のclassのリストで保存。
設置と同時のアビリティ取得や複数回アップグレードなどの特殊な命令は、複数のAlgoを続けてリストに追加している形です。

#csvdata.py抜粋
with open(f"csv/{stage}.csv","r") as file:
    reader = csv.reader(file)

    line = [row for row in reader]

    for lineNo in range(line.__len__()):
        l=line[lineNo]
        if l == []:
            continue
        if not hotkeys.dic.__contains__(l[0]):
            error(f"line {lineNo+1} : \"{l[0]}\" is not defined  (csv/{stage}.csv)")
        if l.__len__() > 2:
            #posありのとき
            pos=(int(l[1]),int(l[2]))
        elif l.__len__()==2:
            #posなしでoptionありのとき
            l.__add__(["",l[1]])
        if l.__len__() > 3:
            #オプションがある時
            if hotkeys.dic[l[0]][0]=="u":
                for i in range(int(l[3]) if hotkeys.dic[l[0]][0]=="u" else int(l[3])):
                    algos.append(Algo(line=lineNo,name=l[0],pos=pos,cate=hotkeys.dic[l[0]][0],hotkey=hotkeys.dic[l[0]][1:],curLoop=i,maxLoop=int(l[3])))
            elif hotkeys.dic[l[0]][0]=="t":
                algos.append(Algo(line=lineNo,name=l[0],pos=pos,cate=hotkeys.dic[l[0]][0],hotkey=hotkeys.dic[l[0]][1:]))
                algos.append(Algo(line=lineNo,name=f"ability{l[3]}",pos=pos,cate="a",hotkey=hotkeys.dic[f"ability{l[3]}"][1:]))
            elif hotkeys.dic[l[0]][0]=="a":
                algos.append(Algo(line=lineNo,name=f"ability{l[3]}",pos=pos,cate="a",hotkey=hotkeys.dic[f"ability{l[3]}"][1:]))
            continue
        algos.append(Algo(line=lineNo,name=l[0],pos=pos,cate=hotkeys.dic[l[0]][0],hotkey=hotkeys.dic[l[0]][1:]))

行動を実行する前にそれを可能か現在地、コスト、ゲームの状態(ポーズ画面やウィンドウがバックグラウンドにあるなど)を画面から読み取ってから判断します。
実行に成功したかを読み取って成功するまで繰り返すという方法も取れますが、あんまりスマートではないと思ってやめました。一応学習目的でもあるので。

#main.py抜粋
def canDo(a:csvdata.Algo):
    tile = ocr.getTileName()
    if a.cate == "g":
        return True
    elif ocr.isPause:
        return False
    elif tile == "":
        return False
    #現在のタイルに不適切な行動の判別と、コスト取得
    elif tile == "PLATFORM":
        if not(a.cate=="t" or a.cate=="m"):
            a.print()
            control.error("unable to do \""+a.name+"\" on "+tile)
        price = ocr.getPuttingPrice(a)
    elif tile == "SOURCE":
        if a.cate != "d":
            a.print()
            control.error("unable to do \""+a.name+"\" on "+tile)
        price = ocr.getPuttingPrice(a)
    elif tile=="ROAD" or tile =="BASE" or tile=="PORTAL" or tile=="MUSIC":
        a.print()
        control.error("unable to do \""+a.name+"\" on "+tile)
    elif hotkeys.dic[tile.lower()][0] == "t":
        if a.cate == "a":
            #アビリティはノーコスト
            return True
        elif a.cate != "u":
            a.print()
            control.error("unable to do \""+a.name+"\" on "+tile)
        price = ocr.getUpgradePrice()
    elif hotkeys.dic[tile.lower()][0] == "d":
        if a.cate != "u":
            a.print()
            control.error("unable to do \""+a.name+"\" on "+tile)
        price = ocr.getUpgradePrice()
    #コスト的な実行不可能の判断
    coin = ocr.getCoinNum()
    if price == None:
        return False
    if coin == None:
        return False
    if a.name == "bounty":
        return coin > price*(bountyNum +1)
    if coin-saving-price>0:
        return True
    return False

また、ウィンドウがアクティブでなくなったときはカウントダウンして終了、ポーズ画面になるとコンソールにcontinueと入力すると続ける、などのユーティリティ的機能をつけました。

# ocr.py抜粋
def foregroundCheck():
    def waitAndRecheck():
        time.sleep(1)
        if hwnd == win32gui.GetForegroundWindow():
            return True
        return False
    if hwnd != win32gui.GetForegroundWindow():
        if waitAndRecheck(): return
        print("exit in")
        if waitAndRecheck(): return
        print("3...")
        if waitAndRecheck(): return
        print("2..")
        if waitAndRecheck(): return
        print("1.")
        if waitAndRecheck(): return
        error("Infinitode2 window is not foreground")

isPause = False
def pauseCheck():
    global isPause
    if pyautogui.locateOnWindow(imagePass("endgame"),"Infinitode 2",grayscale=False,confidence=0.95):
        isPause = True

そして肝心のプレイのアルゴリズムのcsvですが、未完成です。最初の数分はプレイできているのでそのまま追加すれば大丈夫だろうとは思います。ファーム効率のいいステージはマップも広いのでもっと大変ですが。

プレイ画面

AutoInfinitode2プレイ画面

右下の操作ログ以外は単なるプレイ画面なんですが。地味ですね。
所持金(上部のコインのアイコン)の認識のため、全てのエフェクトを非表示にしているので映像でも地味です。

TODO

  • ステージのプレイアルゴリズムの完成
  • ゲームオーバー後に自動的にリスタートするループ
  • 変数とかクラスの名前、ファイルの整理
  • ファーム用ステージのプレイアルゴリズム作成

後書き

夏にBlenderのアドオン開発で初めて本格的に触ったpythonですが、やはり自動化にも手を出してみたいを思って始めました。特に普段の作業で自動化の需要がなかったのでゲームの自動化にしました。
切り抜く画像の座標合わせを始め、何かとゲーム画面を参照しながらやらなきゃならなかったので起動しっぱなしだったんですが、プレイしていないのにSteamでのプレイ時間が20時間くらい伸びてました。起動したまま飯食ってた時間も含めているので実際は不明ですが。

以上、pythonとArduinoと画像認識でタワーディフェンスゲームの自動化をしてみた、でした。
ご読了ありがとうございました。

音MADの話

執筆者: ちょくぽ

これは OUCC Advent Calendar 2022 の 12/9 の記事です。
記事中の〇はただの検索避けです

音MADをつくろう

音MADを作る動機はファンアートなどと同じですが、音MADに使われる技術は他のことにも使えます。基本的にDAWとvocalshifterの技術なので。

  • 合成音声に歌わせる・変な声を出させる
  • 歌の調整をする(人間でも合成音声でも)
  • 普通の動画で音ハメ

などなど……

前提

Q. 音MADって著作権的にどうなの?
A. 元ネタ(素材)の作者に訴えられた場合、確実に負けます。
音MADを作るなら、著作権者に目を付けられないよう、黙認されるよう常に心がけましょう。(要するに二次創作と同じ)

Q. 音MADって儲かるの?
A. 商用利用が許可されている素材だけで作れば広告を付けることはできますが、それが面白くなる確率はかなり低いです。そもそも儲けようと思わない方がいいです。著作権的にも。

Q. 消されないの?
A. 消されます。よっぽどでない限り動画削除だけで済ませてくれますが……

原作者や他のMAD製作者への、可能な限りのリスペクトor配慮をしましょう。
二次創作界の空気感としてもそういう所は特に大事です。というかどの界隈でも発信をするなら半年ROMれ。

海外のフェアユースの思考は日本の感覚とかけ離れているので参考にしてはダメです

音MADの技術の情報はどこで手に入る?

音MADでググるのも良いですが、ニ〇二コだと音楽+コメント付きなので情報が多いです(経験談)。素材はほとんど淫〇ですが。
というか、音MAD自体サムいネタで遊ぶのがメインのような界隈です。勘違いしないように

淫〇や2〇兄貴周りは面白い情報がちょくちょくあります
2〇式動画講座が有能すぎ
純粋な作曲に関する情報も、当然役に立つのでありです

素材集め

素材を集めます。

  1. 原曲
    MADの原曲となる曲を探して、必要な長さのmp3やmp4を入手します。ボカロなどは作者が配布しているケースがありますが、すべての音を手元で作ればかっこいい……(地獄)。

  2. 音声素材
    MADの楽器の音やボーカルにするために、素材を集めます。楽器にするならキャラクターの声質や音圧を重視し、環境音をドラムにしましょう。
    キャラクターに歌わせたいなら、一文字一文字あつめるのが一番早い。歌詞をガン無視して面白いセリフを音合わせする方が楽。

  3. 動画素材
    音MAD制作は音作りがすべてではありません。原作に寄せた動画素材を集めたり、MP4を透過したり……同じジャンルのMAD動画も参考にしましょう。手描きは自信があれば。

二次創作を許可しているコンテンツは探せばかなり多いです。
実際によく使われている素材は、BB配布素材のように権利者に削除する気がないもの、権利者がtwitterでMAD動画に言及しているものなど、セーフ寄りのグレーがほとんどですが……
(ニ〇二コで流行っている例のアレの多くは許可されているわけではないので注意)

ツール

  • REAPER
    DAWならなんでもいいですが、REAPERがおすすめです。有料として紹介されることが多いですが、普通に無料で使えます。

  • VocalShifter
    音声が一定の音程になるよう調整し、素材にできる。音の外しや調子など細かな調整もできる。

  • Wavetone
    テンポ測定や耳コピが必要なら。

  • 動画編集ソフト
    Aviutlなんてソフトは過去の産物なので、普通に最近出たソフトを使いましょう。私はAviutl使います。どれも機能はだいたい一緒です。

メイン

REAPERの操作は覚えれば便利な操作が書ききれないほどあるので、適宜調べてください
file

web上の適当なオンラインBPM計測などで音楽に合わせてクリックしてテンポを解析したら、REAPERに設定します。

原曲(写真上)をREAPERにドロップすれば、グリッド線がなんとなく波形に合います。

REAPER上ではマウスの他に Alt+ドラッグ、S、ctrl+shift+n、ctrl+D、F2、Alt+S などの操作をよく使います。適当に試して体で覚えましょう。

file

素材を母音などで分割し、音楽に合うように配置(大抵の場合、八分音符より細かい調整は必要ないです)
原曲の力を借りているので当然、これだけでMADとして聞けるレベルになります。実際、分割と配置のみでランキング上位になるような音MADも多いです。

エフェクト(ここではVST)でリバーブを付ける、パン(FXの左のつまみ)をトラックごとに左右にずらすなどすると聞きやすくなります

file

次に、音程を変えてみましょう
vocalshifterに素材を渡して素材をダブルクリックすると、ピッチ補正ができます。
黄色い線でピッチを平坦にすれば、音程を変えやすい素材として利用できます。黄色い線(今はD4)がこの音声の音程なので、覚えておきます。

file

これで作成したピッチ調整済wavファイルをREAPERに持っていきます。複数アイテムを選択してF2から一括でwavファイルを変更できます。

アイテムを選択してShift+0、Shift+9で音程を1ずつ変えられるので、MIDIファイルや楽譜、WaveToneなどとvocalshifterの黄色い線を見比べながら調整しましょう。単純作業です。
file

これができたら音作りはできたも同然です。
細かい技術は最初から調べるのではなく、とりあえず触ってから学んだ方が意欲も学習速度も上がります。

場所によって黄色の線を数音高くしたり低くすることでビブラートや感情をつけられます。
クオリティ高い音MADでは、次の手順を踏むものが割とあります。

  • vocalshifterで平坦にした声をREAPERで並べ、切り貼りタイミング合わせだけする
  • それを再度vocalshifterに戻し、音程をつける
  • 音程が大きく飛ぶところではしゃくりや裏返り、伸ばすところではビブラートなどをつける

スケールを外すと目立ちすぎるので、外すときの音は考えましょう。(これ以上は音楽理論の話)

Aviutlの編集は割愛しますが、BPMを指定してグリッド線を小説ごとにする設定項目があります。活用しましょう。
音が鳴るたびに素材を左右反転させる、ちょっとだけ大きくして跳ねさせる、などすると視覚的に楽しくなります。
あとイージングを親の仇のように多用しましょう。
モーショングラフィックス関連のプラグインも沢山あるので、Aviutlで何かを始めるときは即座にプラグインを検索しましょう。

おわりに

いかなる創作においても、数字を見るのはやめましょう。作るのが楽しいのです。
数字は作品のクオリティとは関係なく、題材と宣伝が全てです。マジで。

OUCC Advent Calendar 2022

次回のアドベントカレンダーは12/11、Ayaka氏です!

最近のC#のMainの書き方バリエーション

執筆者: しおかい

これは OUCC Advent Calendar 2022の7日目の記事です。
空いてたので即興で書きました。

最近の(と言っても.NET6とかまでの話ですが)C#にはいろいろ機能が追加されていまして、Hello Worldもいろいろな書き方が出来るようになっています。
dotnet new consoleして出来たProgram.csに

Console.WriteLine("Hello World");

の1行しかなく、にもかかわらず実行できるのを見て戸惑った人も居るのではないでしょうか。

という事で、今回はHello Worldを短く書くための3つの機能を紹介し、最終的に上記の1行コードにしていきます。
なお以下では特記無き限りMainは返り値void、引数string[]としました。

0.プレーンな書き方

最も基本的な書き方はおそらく以下のようになると思います。


using System;

namespace Plane
{
    public class Sample
    {
        public static void Main(string[] arg)
        {
            Console.WriteLine("Hello World");
        }
    }
}

SystemusingConsole.WriteLineでHello Worldしています。
特に言う事は無いでしょう。

1.ファイルスコープ名前空間

上記ではnamespaceでスコープを括っているが、ファイルスコープ名前空間という機能を使えばこの括りを外せ、インデントを1段減らすことが出来ます。

using System;

namespace Plane;
public class Sample
{
    public static void Main(string[] arg)
    {
        Console.WriteLine("Hello World");
    }
}

これは、ファイル全体が名前空間で括られているのと同じ扱いになります。

一応デメリットとして、ファイルスコープ名前空間を使うと1ファイルに1つしか名前空間を(通常の名前空間もファイルスコープ名前空間も)指定できませんが、普通1ファイルに複数名前空間を指定することは無いので問題ないでしょう。

2.ImplicitUsings

C#10からglobal usingという機能が追加されました。
これは例えば

global using System;

とすると、同じプロジェクトにある全てのファイルでusing Systemしているのと同じことになります。
.NET6ではこれを利用してImplicitUsingsという機能が提供されています。
これは読んで字のごとく「暗黙的なusing」であり、いくつかの名前空間を.NETが勝手にglobal usingしてくれるというものです。

コンソールアプリケーションでは

using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

が自動的に行われた扱いになります。
内容はプロジェクトの種類によって異なる様です。
これを利用すると、Hello Worldは以下のようになります。

namespace Implicit;
public class Sample
{
    public static void Main(string[] arg)
    {
        Console.WriteLine("Hello World");
    }
}

using Systemが無くなりました。
幾分すっきりしましたね。

3.トップレベルステートメント

C#9から、トップレベルステートメントと言って、名前空間やクラスや関数の外、トップレベルの場所に直接式(ステートメント)を書けるようになりました。
すなわち、先ほどのHello Worldは、Mainの中身だけ抜き取って以下のように書けます。

Console.WriteLine("Hello World");

これが始めに出てきた1行コードの正体です。
実際にはコンパイル時にclassとMainが自動生成されている様です。
注意点として、トップレベルステートメントより上に名前空間やクラスを定義するとエラーになります。

// これはエラー
namespace TopLevel;
Console.WriteLine("Hello World");
// これもエラー
namespace TopLevel
{

}
Console.WriteLine("Hello World");
// これもエラー
namespace TopLevel
{
    Console.WriteLine("Hello World");
}
// これはセーフ
Console.WriteLine("Hello World");
namespace TopLevel
{

}

また、トップレベルステートメントを書けるのは1プロジェクトに1ファイルまでです。
なお、関数やusingは上に定義できます。

// これはセーフ
void Hello() => Console.WriteLine("Hello World");
Hello();

この関数はプロジェクト全体から見ることが出来ますが、アクセスは出来ないようになっています。

ちなみにこのトップレベルステートメント、awaitを使うと自動生成Mainは勝手にasyncになってくれます。
なので

Console.WriteLine("Hello");
await Task.Delay(5000);
Console.WriteLine("World.");

のようにTaskも使用できます。

ただし、refは直接使用できません。

var hoge = new[]{0, 1, 2};
// エラー
ref var e = ref hoge[^1];
e++;
Console.WriteLine(hoge[^1]);
// これはセーフ
void Hoge()
{
    var hoge = new[]{0, 1, 2};
    ref var e = ref hoge[^1];
    e++;
    Console.WriteLine(hoge[^1]);
}
Hoge();

終わりに

というわけで、C#でHello Worldを簡潔に書けるようになる機能3つを紹介しました。
決まり切ったことを省略できるのは便利で嬉しいことです。

大きく見た目の変わるトップレベルステートメントはともかく、ファイルスコープ名前空間とImplicitUsingsは便利なので使っていきたいと思います。

参考

Arduino Uno R3 で Japanino の水晶発振子を有効化する

執筆者: watamario15 (2022 年度部長)

これは OUCC Advent Calendar 2022 の 4 日目の記事です。結局誰も入れてくれなかったので、自分のストックをそのまま放出します。昨日までは、3 編に分けて OUCC の 3 年間と展望をお届けしておりました。そちらも興味があれば見ていってください。

(ここから本文)

昔入手した Japanino という付録マイコン、単体では何もできない (当たり前) ので放置していたが最近ふと調べてみると、水晶発振子が搭載されているのに使われていないという情報を発見した。そもそもこのマイコンを活用できていない以上何の意味もないわけだが、使えるはずのものが使われていないというのはどうにも気持ち悪いものだ。で、方法を調べてみると fuse ビットを書き換えると有効化できるようだが、それには書き込み装置か別の Arduino が必要らしい。ただ「使えるはずのものが使われていないというのはどうにも気持ち悪い」というだけで購入するようなものではない。ということで、結局諦めていたのだった...

話は変わるが、2022 年春夏学期、実験 B の授業が始まった。COVID-19 の感染状況が読めない中、いつオンラインにせざるを得なくなっても対応できるように毎回部品類を持ち帰るシステムとなった。また、今年からボードを Arduino (Uno R3) に切り替え、内容を一新したらしい。そして、毎回持ち帰る部品類には当然 Arduino も含まれている。

...ここで何か気付くだろうか。

fuse ビットを書き換えると有効化できるようだが、それには書き込み装置か別の Arduino が必要らしい。

あるじゃないか!!まさに今、手元に!!

ということで、[Japanino] 外部水晶発振子を使う: 案山子のメモ帳を参考に 2022 年にこの作業を実施してみたログを残したいと思う。

要件

  • Japanino (持ってなかったらそもそも読んでないと思うけど)
  • Arduino Uno R3 (他のボードは知らない)
  • PC (Uno R3 にスケッチを書き込み、シリアルモニタから操作する)
  • ジャンパワイヤ6本

なお、この手順に従ったことによって発生した損害に関して私は一切責任を負わない。自己責任で実施すること。

手順

  1. 普通に最新の IDE を https://www.arduino.cc/en/software から入手する。Japanino 用は Uno R3 に対応していないので使えない。
  2. https://playground.arduino.cc/Code/Programmer2/ の一番下にある programmer2.txt をダウンロードする。japanino-quartz.ino みたいなファイル名にして、その名前のフォルダに入れておくとそのままスケッチとして開ける。
    • もし削除されてダウンロードできなくなった等の際には、Internet Archive を使う。
  3. ダウンロードしたファイルをスケッチとして Arduino IDE で開き、以下のような編集を加える。
    • 240 行目ぐらいの謎の隙間に #define BYTE 0 と書く。別に void DebugLCD::clear() より前であって、#ifdef の中でなければどこに書いてもいいけど。
    • 450 行目付近の void DoSTK() 関数内の case 'a': 直後に break; を追記する (case 'a': break; でいい)。タイプミスで bootloader をふっ飛ばさないようにするための保険。
    • 1000 行目付近の void Fuse_Arduino_Style() 関数内の case atmega168: で、CMD_Write_Fuse_High( 0xdf);CMD_Write_Fuse_High( 0xdd);CMD_Write_Fuse_Low( 0xef);CMD_Write_Fuse_Low( 0xc6); に書き換える。
    • 1100 行目付近の loader8loader168 (大量の数値が並んでるところ) に const 修飾を加える。要するに unsigned char PROGMEM loader8[]const unsigned char PROGMEM loader8[] に、unsigned char PROGMEM loader168[]const unsigned char PROGMEM loader168[] にすればよい。
  4. Arduino Uno R3 に書き込む。
  5. 以下のように Arduino Uno R3 と Japanino をジャンパワイヤで結線する。Arduino Uno R3 を PC に接続していれば、この結線によって Japanino にも電源供給されるはず。
    Arduino Japanino
     +5V --- +5V
     GND --- GND
     D10 --- Reset
     D11 --- D11
     D12 --- D12
     D13 --- D13
  6. Arduino Uno R3 を PC につないだままの状態で、シリアルモニタを開いて 19200 baud に設定する。もし外してしまっても再接続すればよい。
  7. *s を送信してシグネチャを確かめる。Arduino IDE 2 では Ctrl + Enter が送信。
    • 何も表示されない場合は Arduino Uno R3 を外して Arduino IDE を閉じ、再度 Arduino IDE を立ち上げて Arduino Uno R3 を接続してシリアルモニタを開き、コマンドを再送信してみる。もしそれでもうまくいかなければ、Japanino 用 IDE 等の古いバージョンのシリアルモニタを開いて試してみる。
    • Arduino Uno R3 に書き込んだスケッチはずっと動いているので、IDE の再起動やバージョン変更に伴うスケッチの再コンパイルは不要。
  8. *A を送信する。Writing から始まる文字列が表示されるはず。
    • 小文字にすると全く違う意味となり、手順3で該当箇所の修正を行っていない場合は最悪 bootloader が吹っ飛ぶので注意。
  9. *u を送信し、以下の出力と一致すれば成功(ただし => 以降は補足のための追記)。
    Lock Bits: 11111111 => FF
    Fuse Low:  11000110 => C6
    Fuse High: 11011101 => DD
    Fuse Extn: 11111000 => F8

    逆に以下なら元のままで、変更できていない。以上の手順を再確認してほしい。

    Lock Bits: 11111111 => FF
    Fuse Low:  11010010 => D2
    Fuse High: 11011110 => DE
    Fuse Extn: 11111000 => F8

念のため、書き込んだときに自分が得た出力を掲載する。はじめ最新 IDE では *s*A が利かず Japanino の IDE で実行した画像となっているが、その後最新 IDE でも利くようになった。謎。

出力

あいにく私は精度を確認できる機器を持ち合わせていないため、本当にこれでうまくいっているのかどうかは分からない。実際に確認できた人がいれば、Twitter 等で教えてほしい。

おまけ - Japanino を 2022 年最新環境で使う

以上の作業では Japanino にスケッチを書き込む必要がないため、Japanino 用の環境構築は必要なかった。しかし、実際に Japanino 用に開発を行う際には環境構築が必要になる。以下にその手順を記す。

ドライバ

Japanino 用 IDE に付属するドライバは古いので、https://jp.silabs.com/developers/usb-to-uart-bridge-vcp-drivers の「ダウンロード」から最新のドライバを取得することを推奨する。Windows なら「CP210x Universal Windows Driver」、macOS なら「CP210x VCP Mac OSX Driver」だろう。

ちなみに、インストールは Windows に関しては .inf ファイルを右クリック (Windows 11 ならさらに「その他のオプションを表示」) で「インストール」を選択してもいいし、Japanino を接続すればデバイスマネージャーに警告表示になっているデバイスが現れるはずなのでそれを選択し、「ドライバの更新」からダウンロードしたドライバを選択してインストールしてもよい。前者は何も表示されないのでちょっと心配になるが、Japanino を接続して Arduino IDE が認識すればインストールできている (最新版 IDE なら不明なボード扱いになるが問題ない)。macOS は知らないので、自分で調べてほしい。多分普通のドライバインストールと同じ。

IDE

Japanino 用 IDE を使ってもいいのだが、Arduino IDE 2 が使えるようになった今あの太古の昔の IDE は使いたくないだろう。でも朗報だ。普通に接続して表示されたポートを選び、ボードとして「Arduino Pro or Pro Mini」を選択した上で「ツール -> Processor」から「ATmega168 (3.3V, 8 MHz)」を選べば普通にコンパイルと書き込みが行える。これは VSCode で行う場合も同じ。