今年のお買い物

こんばんは、Rivièreです。
今年も気づけば12月を迎えました。3年目ということで私の中でもすっかりアドカレが風物詩となっています。

皆様いかがお過ごしでしょうか。

この記事はWMMC AdventCalendar 2022 8日目の記事です。
adventar.org

さて、昨日はたまかけくんの「フォトトランジスタの選別機を作った話
tamacake.hatenablog.com
でした。

私も左右のフォトトラの感度の違いに苦しんでいた一人なのですが、きちんと測定するとこんなに波形が異なるのですね!

選定の重要性が伺えます。
きっと私の標準マウスはバラバラな波形を示すことでしょう...

目次

はじめに

さて、今回は今年購入したものについてお話しようと思います。
なお、知識がないので全編小並感となっております。なんか間違ったこと言ってても許してください...

Galaxy A52S

www.galaxymobile.jp

Galaxyのミドルクラスの端末です。
これまでXZ1を使用していましたが、さすがに5年以上経過していたので購入しました。
ソシャゲをしない自分にとっては十二分なスペックです。

5年経過しているとはいっても電源も動作もそれなりに安定していたので、届くまではまだ行けたかなと思っていたのですが、やはり使いだすと多くの点で進化を体感できました。

最もそれを感じたのはやはりディスプレイです。
XZ1にはそこそこのベゼルがあったのですが、A52Sはベゼルレスで、内カメまわりにまで画面が広がっています。
このため端末サイズの変化以上に画面サイズが大きくなりました。

有機ELディスプレイの繊細さにも感動しました!
ピッチがまったく感じられません。正直もうXZ1には戻れませんね...

薄型化のために廃止の傾向にあるイヤホンジャックも搭載しているのでもう文句のつけようもないです。
気に入っています。

Meta Quest 2

www.meta.com
https://scontent-nrt1-1.xx.fbcdn.net/v/t39.8562-6/308669029_795109961727528_3389105350973284289_n.jpg?_nc_cat=101&ccb=1-7&_nc_sid=6825c5&_nc_ohc=Dj2Da3JMOAwAX-pL_Lp&_nc_ht=scontent-nrt1-1.xx&oh=00_AfAmd9AhG0IPqNhHx8GLypK5bNUeGVQ0UEJcoM761kfCtQ&oe=6395B9DF

以前からVRに興味があったので思い切って購入してみました。
が、思ったほど使っていません...

これだ!というタイトルに出会えていないためです。

BeatSaverは面白いのですが、カスタム曲に手を出すと一気に難度が...
もともとやりたいと思っていたVRChatをできていないので、いろいろ落ち着いたらまた触っていきたいと思ってます。

WF-1000XM4

www.sony.jp
https://www.sony.jp/products/picture/WF-1000XM4.jpg
もともとWF-1000XM3を使用していたのですが、うっかり洗濯してしまって片耳のノイキャンが効かなくなってしまったので購入しました。

前作から大きくデザインが変更され、ケースも小型化されました。音質もLDAC対応で良くなっていると思います。

ノイキャン性能に関しては申し分ないです。電車内を除いては。
音楽を流すと、完全に外部の音が聞こえないのがやはりいいですね。

ノイキャンがないともう生きていけないです。
ただXM3以上に車内アナウンスが貫通してくるような気がします。

ただし、冬の時期になって発覚したのですが、気密性の高いマスクを使用していると、マスクから漏れる息でタッチ検出されてしまうという時勢柄なかなか辛い問題を抱えています。
なんとかアップデート等で対応してくれないでしょうか...

アップデートといえば今冬マルチポイントに対応することが発表されています。
これまでマルチポイントのものを使用したことがないので密かに楽しみにしています。

SednaEarfit Crystal

www.aiuto-jp.co.jp
https://www.aiuto-jp.co.jp/resources/products/3487_8b440e51ae83c65c7fee351a9b48b7d088f02694_ll.jpg

WF-1000XM4に付属していたノイズアイソレーションイヤーピースを使用していると、耳が痛くなってくるので購入しました。初イヤピです!

他のイヤピよりも抜きんでて高いのですが、ちょうど購入したのが新製品のSednaEarfit Max
www.aiuto-jp.co.jp
が登場したタイミングで安くなっていたのと、イヤホンに求めている密閉感がMaxにはなかったのでこちらを選びました。

逆に言えばMaxはさらっとしていて本当につけている感覚がなくなるので、そういったものを求めている方にはおすすめだと思います。高いですが。

正直、イヤーピースで音変わらないと思っていたのですが、結構変わって驚いています。
付属のものよりこちらのほうが音に立体感を感じます。
サイズが細かく選べるので、購入前にはヨドバシ等で視聴するのがいいと思います。

普段使っているイヤホンの音を変えてみたいという方はイヤーピースを変えてみるのもいいかもしれません。
メジャーどころでも1000円程度のものがほとんどだと思います。

ただ、XM4で使うとなると、ノイズアイソレーションイヤーピースのパッシブに大きく依存していたのもあって、ノイキャン性能は落ちます。

PD65W充電器

https://www.amazon.co.jp/gp/product/B09F9HSPSQ/ref=ppx_yo_dt_b_asin_title_o07_s00?ie=UTF8&psc=1
https://m.media-amazon.com/images/I/61Bv2EwCoRL._AC_SS450_.jpg


待望のPD充電器&USBケーブルです。
私は携帯、タブレットだけでなく、PCもtype-Cで充電してます。
そのため、これ一台持つだけでわざわざ重たいACアダプターを持ち運ぶ必要がなくなりました。

携帯やタブレットもPDに対応しているので1時間もすれば充電が満タンになります。もう最高です。

AT2035

www.audio-technica.co.jp
https://www.audio-technica.co.jp/upload/contents/product/AT2035/product_image_1635821914.jpg?1635821914

Audio Technicaのエントリークラスのコンデンサーマイクです。
もともとZOOMやゲームのVCをする際にはヘッドホンのマイクを使っていたのですが、ケーブルを断線してしまったので使用できなくなってしまい、せっかくならいい物をと購入した次第です。

AT2035は一応定番のAT2020の上位版的な位置づけになっています。
ローカット等の機能も付いていますし、集音性に関してはさすがコンデンサーマイクといったところです。

また、専用のショックマウントが付属しているのも嬉しい点です。
なおAT2020にショックマウントを追加で買うと価格差が3000円くらいしかありません。

マイクアームはキョーリツのグースネックのもの
https://amzn.asia/d/5eccbFq
を購入しました。

グースネックタイプなのでよくあるマイクアームより視界を塞ぎにくく気に入ってます。
机によっては長さが物足りないかもしれません。
グースネックなので寿命が気になりますが、今のところきちんと高さを保ってくれています。

Focusrite Scalett Solo Gen3

focusrite.com
https://focusrite.com/sites/focusrite/files/scarlettsolo-hero-806-330.png

Focusriteのエントリークラスのオーディオインターフェースです。
At2035を使用するにあたって、+48Vのファンタム電源が必要になるのでそれ目的で一緒に購入しました。

192kHz, 24bit再生可能なのでハイレゾ再生可能です。また、つまみで音量調整できるのが非常に楽です。

HPH-MT8

jp.yamaha.com
https://jp.yamaha.com/files/CEDB3BFE7B434A8898C5AFA70F7F0E02_12073_1fe837732f6e3d822b3e61afa6cdff0e.jpg?impolicy=resize&imwid=735&imhei=735

YAMAHAのモニターヘッドホンです。
私は音楽的な表現が昔からどうしても得心が得られず、例えば「音の粒立ち」であったり、「音場が広い」などという言葉を耳にしてもどういう状態なのか全く理解できませんでした。
しかし、そのような違いを体感できれば音楽をもっと楽しめるだろうと常々思っていたので、この機会に思い切って良さげなヘッドホンを買ってみました。

結果として、ほかのヘッドホンで聴いていた時とは全く違う体験ができました。
同じ曲を聴いているはずなのにこれまで全く気づけなかった音や息遣いがはっきりと聴こえてきて、発見の連続です。

そして、音の切れ目というか、音を塊のように体感することができました。
これがおそらく「音の粒立ち」ですかね。これにはかなり感動しました。

どうせならもっと違いを体感したい!と思い、これまで避けていたサブスクにも登録してみました。
Amazon music unlimited
www.googleadservices.com
です。

音楽配信サービスを初めて利用したのですが、Youtubeと違って垂れ流しにしていても広告が入ったり、関係ない動画に飛んだりしないので作業に集中できます。

INNOCN 27C1U-D

INNOCN 27" Computer Monitor 4K - 27C1U-Dinnocn.com
INNOCNの4Kモニターです。
ブラックフライデーで安くなっていたので購入しました。

ゲーミングモニターを所持しているのでサブモニター目的に作業の効率化ができればと思って購入しました。
実際に2画面構成にして、これまで新しい作業をするために裏に隠していたウィンドウを開いたままにできるので、作業中のストレスがなくなりました。

また、4Kなので細かく画面分割しても文字が潰れないのがいいです。
FHDとの併用なので画面移動が億劫になるかなとも当初思っていたのですが、全く気になりません。
2つのモニターをまたいで表示する用途の場合は確かに合わないかもです。

また、DCI-P3 98%のカバー率ということでなかなかつよつよっぽいです。素養がないのであまりわかりませんが...

終わりに

コロナで外出が減ってしまったこともあり、特に今年は生活環境への投資に意識が向いていたようです。
来年こそはイベンターに復帰したいですね...

明日は玉ちゃんの「まっきんとっしゅ」です。
洋服の話でしょうか...?洋服に関して知識がないので楽しみです...!

ジャッジ―さん、今年もアドベントカレンダーの開催ありがとうございました!
来年も楽しみにしています。

それでは。

今週のお題「買ってよかった2022」

Stream Deckを自作する【M5Stack Core2】

こんばんは、Rivièreです。好きなUIはSHIGUREです。

すっかり寒くなりましたが、皆さんいかがお過ごしですか?

私は今年も自宅で作業することが多く、季節を感じぬままここまできてしまいました。

気付けば明日はもうクリスマスイブですね。

 

この記事はWMMC AdventCalendar2021 23日目の記事

adventar.org

です。

 

さて、昨日、一昨日は、ぱわぷろさんのWMMC活動の振り返り記事でした。

21日目:

個人的WMMC活動を振り返る(ステッパー編) - ぱわぷろ活動日誌

22日目:

個人的WMMC活動を振り返る(DCマウス編) - ぱわぷろ活動日誌


私が所属する以前の顛末も書かれていて、先輩方の苦労の一端を知ることができました。すでに老害に片足を突っ込んでしまう時期になりましたが、何とかこのサークルが存続できるように尽力していければと思います。

 

また、私が直前まで題材を決めていないという怠惰を晒したために、ぱわぷろさんには「再び未定」という謎の題でブログを書くと伝わってしまいました。

ごめんなさい、別に哲学っぽい何かを語るとかいうわけではなかったんです...

 

というわけで、M5Stack Core2 を使って遊んでいきます。

 

目次

 

はじめに

今回は、M5Stack Core2 を使ってStream Deckのようなものを作っていきます。

とはいえなんぞやという方も多いかと思いますので、まずは軽く紹介します。

 

M5Stack Core2

M5Stack Core2は2020年9月にM5Stack社から発売されたESP32モジュールです。

タッチスクリーンや6軸IMU、ブザー、マイク、内部モーター、microSDスロットなどなどハードウェア周りが充実しています。

また、ESP32を使用しているので、WiFiBluetoothが標準搭載されており、背面からGPIOを扱うことも可能です。

さらにstackの名の通り、同社では様々な拡張基板やモジュールが販売されており、組み合わせることで多くの機能を後付けすることが可能です。

本製品はArduino、UiFlow、MycroPythonを使って開発することができます。

まさに遊ぶには持って来いのモジュールですね。

 

www.switch-science.com

 

Stream Deck

Stream Deckとは、Elgato社から販売されているPCコントローラーです。

液晶付きのボタンが並んでいるのが特徴で、ユーザーは専用ツールで各窓にさまざまな機能を割り振ることができます。

OBS操作等も簡略化できることから、その名の通り、ストリーマーを中心に人気の商品です。しかし、アプリケーションの起動や、キーボードショートカット、その他様々な操作を登録することができるため、一般のユーザーにもその人気は広がっています。


www.elgato.com

とにかく作ってみる

Stream Deckは便利そうな反面、少しお高めです。なので自作します。

 

方針としては、M5Stack Core2上のボタンを押すとボタンに固有の文字列がPCに送信され、それをトリガーとして各種の動作を実行させます。

 

プログラムは

shizenkarasuzon.hatenablog.com

を参考にしています。通信の方針はこちらの記事に沿うものですので、通信に関する内容はこちらを参照してください。

UDP(User Datagram Protocol)

UDP(User Datagram Protocol)は通信方式(プロトコル)の一つです。よく対比されるものとしてTCP(Transmission Control Protocol)があります。UDPTCPに比べパケットロスが発生しやすいものの、TCPよりも速く通信することができます。

 

開発環境

・Windows11

Visual Studio Code 1.63.2 (Python3.10.1)

Arduino IDE 1.8.15

 

PCのIPアドレスの確認

まず、PCのIPアドレスを確認します。

WindowsではコマンドプロンプトMacではターミナル上で、ipconfigと入力して実行します。

実行結果のIPv4 アドレスが今回使用するPCのIPアドレスとして用いる値です。

M5Stack Core2のプログラム(送信側):ArduinoC++

#include <M5Core2.h>
#include <WiFi.h>
#include <WiFiUDP.h>
#include <I2C_AXP192.h>


I2C_AXP192 axp192(I2C_AXP192_DEFAULT_ADDRESS, Wire1);

const char ssid = "WiFiSSID"; //接続するWiFiSSID
const char pass = "WiFiのパスワード"; //接続するWiFiのパスワード

WiFiUDP wifiUdp;
const char *pc_addr = "接続先のIPアドレス"; //接続先のデバイスIPアドレス
const int pc_port = 50007; //送信先のポート
const int my_port = 50008; //自身のポート

//Button Color
ButtonColors cl_on = {0x7BEF, WHITE, WHITE}; // タップ時の色(背景, 文字列, 枠)
ButtonColors cl_off = {BLACK, 0xC618, 0xC618}; //離した時の色(背景, 文字列, 枠)

//ボタン定義(X軸, Y軸, 横幅, 高さ, 回転, ボタンのラベル, 離した時の色, タッチ時の色)
Button btn_UiShig(50, 70, 100, 100, false , "UiShig", cl_off, cl_on);
//UiShig
 
Button btn_End(180, 3, 100, 60, false , "End", cl_off, cl_on);
//PC側のプログラム終了&M5stackの電源OFF
 
Button btn_PCSleep(50, 3, 100, 60, false , "Sleep", cl_off, cl_on);
//PCをスリープ
 
Button btn_TabMove_R(180, 70, 100, 100, false , "TabR", cl_off, cl_on);
//Ctrl+Tab
 

//ボタンイベント
void event_btn_UiShig(Event& e) {
  // ボタンが押された(指が離されたタイミング)時の処理
  wifiUdp.beginPacket(pc_addr, pc_port);
  wifiUdp.write('u');
  wifiUdp.write('i');
  wifiUdp.endPacket();
}

void event_btn_End(Event& e) {
  // ボタンが押された(指が離されたタイミング)時の処理
  //終了コマンド送信
  wifiUdp.beginPacket(pc_addr, pc_port);
  wifiUdp.write('e');
  wifiUdp.write('n');
  wifiUdp.write('d');
  wifiUdp.endPacket();
  delay(1000);
  axp192.powerOff();
}

void event_btn_PCSleep(Event& e) {
  // ボタンが押された(指が離されたタイミング)時の処理
  wifiUdp.beginPacket(pc_addr, pc_port);
  wifiUdp.write('P');
  wifiUdp.write('C');
  wifiUdp.write('S');
  wifiUdp.write('l');
  wifiUdp.write('e');
  wifiUdp.write('e');
  wifiUdp.write('p');
  wifiUdp.endPacket();
}

void event_btn_Twitter(Event& e) {
  // ボタンが押された(指が離されたタイミング)時の処理
  wifiUdp.beginPacket(pc_addr, pc_port);
  wifiUdp.write('t');
  wifiUdp.write('w');
  wifiUdp.write('i');
  wifiUdp.write('t');
  wifiUdp.write('t');
  wifiUdp.write('e');
  wifiUdp.write('r');
  wifiUdp.endPacket();
}

void event_btn_TabMove_R(Event& e) {
  // ボタンが押された(指が離されたタイミング)時の処理
  wifiUdp.beginPacket(pc_addr, pc_port);
  wifiUdp.write('t');
  wifiUdp.write('a');
  wifiUdp.write('b');
  wifiUdp.write('R');
  wifiUdp.endPacket();
}

void setup() {
  M5.begin();

  //---------------Button------------------
  M5.Buttons.setFont(FSSB12); // 全てのボタンのフォント指定
  //btn_UiShig.setFont(FSSB24); //ボタンのフォントを個別指定したい場合
  btn_UiShig.addHandler(event_btn_UiShig, E_RELEASE); //イベント指定
  btn_End.addHandler(event_btn_End, E_RELEASE);
  btn_PCSleep.addHandler(event_btn_PCSleep,  E_LONGPRESSED);
  btn_PCSleep.longPressTime = 2000;
  btn_TabMove_R.addHandler(event_btn_TabMove_R, E_RELEASE);
  M5.Buttons.draw(); // 全てのボタンを描画
  //---------------------------------------

  WiFi.begin(ssid, pass);
  while ( WiFi.status() != WL_CONNECTED) {
    delay(2000);
    WiFi.begin(ssid, pass);
  }
  M5.Lcd.setCursor(0, 230);
  M5.Lcd.setTextSize(1);
  M5.Lcd.println("WiFi connected");
  wifiUdp.begin(my_port); //UDP通信の初期化処理
}

void loop() {
  M5.update();
}

 

PCのプログラム(受信側):Python

import sys
from socket import *
import time
import webbrowser
import ctypes
import pyautogui

print("start network")
addr = ("",50007) #ポートの指定

print("network setup started")
UDPSock = socket(AF_INET, SOCK_DGRAM)
UDPSock.settimeout(0.0001)
print("connected.")
print("Network info -->" + str(addr) )
UDPSock.bind(addr)

print("Setup finished.")

while True:
    try:
        (data, addr) = UDPSock.recvfrom(1024) #UDP受信受けつけ
    except timeout:
        continue

    if addr != 0:
        str_data = data.decode('utf-8')
        print(f"get massage from {addr} --> {str_data} ") #受信情報表示

    if str_data == "ui": #UiShig
        webbrowser.open(url)  
   
    if str_data == "tabR": #ctrl+Tab
        pyautogui.keyDown('ctrl')
        pyautogui.press('tab')
        pyautogui.keyUp('ctrl')

    if str_data == "PCSleep": #PCをスリープする
        ctypes.windll.PowrProf.SetSuspendState(0, 1, 0)
   
    if str_data == "end": #プログラム終了
        break

print("end.")

プログラムの流れ

それぞれのプログラムの流れをざっくりとまとめると

〇送信側(M5Stack Core2)

UDP通信設定

・ボタンの定義

    -UiShig: "ui"と送信

    -End: "end"と送信し、1秒後にM5Stackの電源OFF

    -TabR: "tabR"と送信

    -Sleep: 長押しで"PCSleep"と送信

■setup()

・ボタンのイベント実行タイミング

WiFi接続の開始

■loop()

・画面情報の更新

 

〇受信側(PC)

UDP通信設定

■While

・受信待機

    - "ui" と受信: Uishigを表示

    - "tabR" と受信: CTRL+TAB とキータイプ

    - "PCSleep"と受信: PCをスリープさせる

    - "end"と受信: while文から抜け、プログラムを終わる

 

それでは実行

プログラムが完成したところで実行してみます。

実行画面

M5Stack Core2を起動すると以下のようにボタンが表示されます。

WiFiへの接続が確立するとWiFi connectedと表示されます。

f:id:argoship25:20211222232822j:plain

M5Stack Core2の実行画面

さらに、PC(受信側)のほうでもプログラムを実行します。

start network
network setup started
connected.
Network info -->('', 50007)
Setup finished.

これでPC側も待機状態になりました。

 

動かす

これでいつでも緊急脱出できますね。

おわりに

いかがだったでしょうか。

無事Stream Deckのようにボタンを押してPCを操作することができました。

しかも、本家とは異なり、PC前にいなくともPCを操作することが可能となりました!

(それはもはやStream Deckじゃなくてリモコンなのでは...?)

ただ物理ボタンでなかったり、自由度が高い反面行わせたい動作によっては複雑なプログラムになるなど、やはり既製品にはない困難もあります。

 

しかし、M5Stackはライブラリが充実しているほか、扱いやすいArduinoや事前知識ゼロからでもプログラムできるUiFlowを使用することができるので、手ごろにIoTを体験してみたい方はぜひ挑戦してみてください。

多くの方が様々なものを作っていますので、まずは覗いてみるのもいいかもしれません。

 

今回は、WEBページの表示やシャットダウンという操作だけでしたが、プログラムを書き換えることで様々な動作を実現することができます。

今後は相互的にUDP送受信を行わせたり、マウス開発との連携も行っていきたいです。最も今回作成したようなコントローラーはマウスには不要なのですが。

 

明日、クリスマスイブの記事は、aluminumくんの

CADデータをクラウド管理してみた

です。

CADデータのクラウド管理については考えたことがありませんでしたが、たしかに内蔵メモリに保存して消えたときには目もあてらませんね。精神的ダメージが半端ない...

季節柄きっと絵師様の気持ちを理解できるに違いありません。

 

それでは。

 

蛇足:

ういしぐれはいいぞ

www.youtube.com

rand関数で迷路をランダム生成させてみた


みなさんはじめまして。Rivièreです。
ブログ初投稿につき、ご不便がありましたら申し訳ありません。

この記事は
WMMC Advent Calendar 2020 - Adventar
の6日目の記事です。

昨日は、まんぼーさんによる
2020年に勉強した本の紹介 - まんぼーの技術記
でした。

制御からプログラミング、また私のような諸学者向けの本まで紹介してくれています。勉強しなきゃ...!
特にいつも雰囲気でプログラムを書いてしまう自分には、リーダブルコードが気になります。
そんな私の書く今回のブログは、プログラミングがテーマなのですが...

目次

はじめに

まず、今回の内容なのですが、初心者が好き勝手弄った結果、なんかうまくいったものを紹介するというものになります。
理論的に本当に問題が解決できたのかは定かではないのですが、とりあえずrand関数をそのまま使うよりはよい結果になった気がします。(小並感)

今回のテーマ

さて、今回のテーマですが、
rand関数を使って迷路をランダム生成させてみる
というものになります。

これは、マウスともっとお話しできるようにしたいというのがモチベーションです。
あと、二次走行の調整をしてるときに何度も一次走行させるのが面倒だったので、ならば最初から迷路情報を与えられるようにしておきたいという思いもありました。

rand関数について

今回はC言語を用い、乱数の生成に stdlib.h で用意されているrand関数を使っていきます。
Excel内の関数としても採用されているrand関数は、機械に精通していない人でも一度は目にしたことがあるのではないでしょうか。

このrand関数は乱数の種と呼ばれる値を与えることによって、0からRAND_MAX(stdlib.hで定義)の値をとる疑似乱数を生成してくれます。
具体的には次のアルゴリズムに従って疑似乱数が生成されます。

 X_n + 1 = (A \times X_n + B)modM

ここで、A、B、Mはコンパイラごとに設定された定数であり、乱数の種は X_0にあたります。
このアルゴリズム線形合同法と呼ばれるものです。
参考: 計算研究会 - C言語のrand関数について
参考: 乱数の仕組み - Qiita

重要なのは、

  • このようにして生成される疑似乱数列には周期があり、rand関数の周期は比較的短いこと
  • 同じコンパイラを使用して同じ乱数の値を与えると、同じ疑似乱数列が得られること

です。

より周期が長く、品質の良い乱数アルゴリズムとしてメルセンヌ・ツイスタ等が知られています。

メルセンヌ・ツイスタの使用法については、

等を参照してください。

全体の構成

見通しをよくするために、プログラムの概要を説明します。

主要な構成は、

  • 迷路をランダム生成させる関数

void make_map (int goal_x, int goal_y)

  • 作成した迷路を検証する(歩数マップを作成する)関数

int step_map (int goal_x, int goal_y)

  • main関数

となっています。

流れとしては、壁をランダム生成させて、それが迷路の形を成しているかを検証し、もしうまくいってなければ再生成させるというものになります。

ここでは説明しませんが、ゴール座標(goal_x, goal_y)の設定などもしておき、main関数内で引数として渡します。

とりあえず作ってみる

それでは作ってみます。
発生させた乱数の値によって壁を配置するか否かを決定します。

グローバル変数

    #define SIZE 16 
    int rand_coeff = 10;

基礎設定

今回はmap[SIZE][SIZE]のような二重配列を用いて座標を指定し、その座標ごとに周囲を囲む壁の情報を与えていきます。
この情報を与えるために、map[SIZE][SIZE]は、

struct NEWS{
    int n;
    int e;
    int w;
    int s;
    int step;
};

struct NEWS map[SIZE][SIZE];

という構造体配列として宣言しておきます。

ただし、以下の点に注意が必要です。

  1. 迷路の外周には壁がある。
  2. 迷路という特性上、隣同士の壁情報は連続している必要がある。(一つ右の位置において西壁があるのならば、現在地には東壁がある)
  3. スタートからゴールまでつながっている必要がある。

迷路ランダム生成関数の作成(make_map)

使用する変数

    float n_temp, e_temp, w_temp, s_temp;
    int x,y;
    int i;

実装

まず、壁情報を-1で初期化します。
以降

  • 壁があるときは1
  • 壁がないときは0
  • 未設定のときは-1

を格納することにします。

    for(x=0; x<SIZE; x++){
        for(y=0; y<SIZE; y++){
            map[x][y].n=-1;
            map[x][y].e=-1;
            map[x][y].w=-1;
            map[x][y].s=-1;
        }
    }

つぎに、外周に壁を配置します。

    for(i=0; i<SIZE; i++){
        map[i][0].s = 1;
        map[0][i].w = 1;
        map[i][SIZE-1].n = 1;
        map[SIZE-1][i].e =1;
    }

また、マイクロマウスではスタート地点では北壁のみ壁がないという設定になっていますので、これに準拠します。

    map[0][0].n = 0;
    map[0][0].e = 1;
    map[0][0].w = 1;
    map[0][0].s = 1;

壁のランダム配置(make_map)

壁をランダムに配置していきます。
ここで慣例では、乱数の種はtime(NULL)で与えます。

しかし、失敗した場合には再び迷路を作成するためにmake_mapを実行しなければなりません。
この間、システム上の時間経過は1秒に満たず、乱数の種は同一のものが与えられるために同一の乱数が生成され、その結果全く同じ壁の配置がなされてしまいます。

これを避けるために、座標(x,y)やら係数(rand_coeff)やらをかけて無理やり変化させます。

また、各n_tempe_tempw_temps_tempには0から1未満の値をとるように規格化した乱数値を格納します。

            switch(x*y){
                case 0:
                    if(x == 0 && y != 0){
                        srand(y*rand_coeff*((unsigned)time(NULL)));
                    }else if(x != 0 && y == 0){
                        srand(x*rand_coeff*((unsigned)time(NULL)));
                    }else{
                        srand(rand_coeff*((unsigned)time(NULL)));
                    }
                break;

                default:
                    srand(x*y*rand_coeff*((unsigned)time(NULL)));
                break;
            }

            n_temp = rand()/ (RAND_MAX + 1.0);
            e_temp = rand()/ (RAND_MAX + 1.0);
            w_temp = rand()/ (RAND_MAX + 1.0);
            s_temp = rand()/ (RAND_MAX + 1.0);

ここで先ほどの注意に従って壁を配置していきます。以下for文継続です。

  • すでに壁が挿入されているときは何もしない
  • 配置する方向の1マス隣の逆方向の壁(東壁の設定をするときは右隣りのマスの西壁)があるときは、壁を配置
  • 上記以外のときランダム生成

いま、乱数値を規格化しているので、だいたい50%の確率で配置したいとすると、
n_temp等が0.5より大きいときには配置することにすれば実装できます。


以下は北壁の設定についてです。

            /*Decide the state of north for each area.*/
            if(map[x][y].n != -1){
                //if Wall info is already installed.(pass)
            }else if(map[x][y+1].s != -1){
                map[x][y].n = map[x][y+1].s;
            }else if(n_temp < 0.5){
                map[x][y].n = 0;
            }else{
                map[x][y].n = 1;
            }

上記を東壁、西壁、南壁についても実行します。

その後、

            rand_coeff+=100;

とsrandの引数にかかるrand_coeffの値も増やしておきます。
これはグローバル変数として宣言したので、値は関数を出ても保持されます。

for文はここで閉じます。

ここまでで一応壁の設定はできました。迷路を作成する関数はここまでになります。

作成した迷路の確認(step_map

壁の設定が終わったものの、迷路になっているかは別問題です。
迷路はスタートからゴールまでつながっていないといけないからです。

使用する変数

    int x, y;
    int min_step =0;

以下では迷路を確認する関数step_mapを作っていきます。

確認方法はいくつかあると思うのですが、ここでは歩数マップを展開することで確認します。

歩数マップの値はmap[SIZE][SIZE].stepに格納します。

まずは、-2で初期化します。

    for(x=0; x<SIZE; x++){
        for(y=0; y<SIZE; y++){
            map[x][y].step = -2;
        }
    }

歩数マップの作成

次に歩数マップを作成していきます。

各座標について0から順に、歩数値が入るかどうかを検証(つまり歩数値がnとなる場所がどこかを検証)しますが、先ほど述べたのように解けない場合というものが存在します。

終了条件をスタート地点の歩数値が記入されることとしているので、解けていれば総座標数よりも大きい歩数値の検証は行われないはずです。

したがって、総座標数よりも大きい歩数値の検証を始めた段階でどこかで迷路が閉じてしまっていると考えられます。

このような考えのもとに実装したのが以下のコードです。

    /*Set the goal step*/
    map[goal_x][goal_y].step = 0;

    /*Set the steps from goal until the start step is determined.*/
    while(map[0][0].step == -2){
        for(x=0; x<SIZE; x++){
            for(y=0; y<SIZE; y++){

                //check only for minimum step areas
                if(map[x][y].step == min_step){

                    //check north area
                    if(map[x][y].n == 0 && map[x][y+1].step == -2 ){
                        map[x][y+1].step = min_step + 1;
                    }

                    //check east area
                    if(map[x][y].e == 0 && map[x+1][y].step == -2){
                        map[x+1][y].step = min_step + 1;
                    }

                    //check west area
                    if(map[x][y].w == 0 && map[x-1][y].step == -2){
                        map[x-1][y].step = min_step + 1;
                    }

                    //check south area
                    if(map[x][y].s == 0 && map[x][y-1].step == -2){
                        map[x][y-1].step = min_step + 1;
                    }
                }

            }
        }

        /*if the start is not connected from start to goal*/
        if(min_step>SIZE*SIZE){
            // the maze is not solved
            return -2;
        }
        min_step++;
    }

main関数

使用する変数

    int goal_x, goal_y;
    int status = -1;

main関数内では、上記で作成した関数を

    while(status != 0){
    make_map(goal_x, goal_y);
    status = step_map(goal_x, goal_y);
    }

のように回します。

以上で主要なプログラムの完成です。

作成した迷路

このようにして作成した迷路の例を張っておきます。
f:id:argoship25:20201205204006p:plain
f:id:argoship25:20201205204345p:plain

おわりに

いかがだったでしょうか。
今回はrand関数を用いてランダムな迷路を作成するという内容でした。
かなりやっつけでしたが、無事にrand関数でもランダムに見える迷路を作成できたように思います。

ここまで書いてから気づいたのですが、マイクロマウスでは4マスゴールかつ柱に最低一つの壁を設置する必要がありましたね...
それはまた後日ということで...

WMMC Advent Calendar 2020 - Adventar 7日目は
aluminum君による BOOX Note2のレビュー です。

昨今需要が高まっているタブレット端末の記事とあって、個人的に非常に楽しみです。

それでは。