aburi6800のブログ

コンピュータのプログラミング、ゲームに関するニッチな情報を書いていくブログです。

【ubuntu】ubuntu20.04にEPSON EP-10VAのドライバをインストールする

ubuntu20.04をインストール後、自動的にプリンタを検出して印刷できていたが、先日改めてLibreOffice Calcで印刷したところ、何やら化けた文字が印刷されてしまった。
原因が不明なので、改めて以下手順でドライバをインストールした。

ドライバの入手元

以下にアクセス。

http://download.ebz.epson.net/dsc/search/01/search/searchModule

製品名・型番に「EP-10VA」を入力、OSは「Linux」を選択して虫眼鏡アイコンをクリック。

f:id:aburi6800:20210220164314p:plain

プリンタドライバ

下記の画面で一番上の「Printer Driver」のDownloadボタンをクリック。

f:id:aburi6800:20210220164345p:plain

「同意する」ボタンを押すと下にファイルの一覧が表示されるので、「epson-inkjet-printer-escpr_1.7.9-1lsb3.2_amd64.deb」(上から4番目)の「ダウンロード」ボタンをクリック。

ubuntu以外を使っている場合は、使用しているディストリビューションに合わせて対象のファイルを選択する。

f:id:aburi6800:20210220164417p:plain

以下を実行し、必要なLSBパッケージをインストールする。

sudo apt-get install lsb

ダウンロードした.debパッケージを、以下のコマンドでインストールする。

sudo dpkg -i ./epson-inkjet-printer-escpr_1.7.9-1lsb3.2_amd64.deb

ユーティリティ

下記の画面で二番目の「Epson Printer Utility」のDownloadボタンをクリック。

f:id:aburi6800:20210220164345p:plain

「同意する」ボタンを押すと下にファイルの一覧が表示されるので、「epson-printer-utility_1.1.1-1lsb3.2_amd64.deb」(上から4番目)の「ダウンロード」ボタンをクリック。

f:id:aburi6800:20210220164537p:plain

ダウンロードファイルのあるディレクトリで以下を入力し、インストールする。

sudo dpkg -i ./epson-printer-utility_1.1.1-1lsb3.2_amd64.deb

スキャナドライバ

下記の画面で三番目の「Scanner Driver」のDownloadボタンをクリック。

f:id:aburi6800:20210220164345p:plain

「同意する」ボタンを押すと下にファイル(マニュアル)の一覧とダウンロードリンクが表示されるので、「Package Download Page」のリンクをクリック。

f:id:aburi6800:20210220164705p:plain

スキャナドライバダウンロードページに移動するので、使用しているディストリビューション(今回の場合、ubuntu 20.04(LTS))のDownloadボタンをクリック。

f:id:aburi6800:20210220164741p:plain

ダウンロードファイルのあるディレクトリで以下を入力し、インストールする。

tar -xvf ./imagescan-bundle-ubuntu-20.04-3.65.0.x64.deb.tar.gz
cd imagescan-bundle-ubuntu-20.04-3.65.0.x64.deb.tar.gz
./install.sh

シェルスクリプトでのインストールが失敗する場合は、マニュアルに従って手動でインストールすることも可能。

プリンタの設定

設定でプリンタの画面を開き、ウィンドウ上部の「追加」ボタンをクリック。
※この画面で表示されている「EPSON_E…A_Series」は自動的に登録されていたものだが、これがうまく動かなかった

f:id:aburi6800:20210220164837p:plain

EP-10VAを選択。

f:id:aburi6800:20210220164920p:plain

しばらく待つと登録が完了する。
登録された「EP-10VA」の詳細が以下の内容になっていればOK。

f:id:aburi6800:20210220164945p:plain

これをデフォルトプリンターに設定して完了。

【Obsidian】Obsidian gitプラグインのメモ

マークダウンエディタのObsidionは、ドキュメントの作成や思考の整理に適したツールですが、基本的に保存先はローカルドライブになります。
そのため、複数台のPCを使用していると、ファイルの同期を何らかの方法で行う必要があります。
また、バックアップも自己管理となります。
この問題を解消するため、サードパーティープラグインとして公開されている「Obsidian git」プラグインについて簡単に説明します。

2022/04/11 追記: この記事を記載後、プラグインのバージョンアップが進み、機能が充実しています。
設定項目もその分増えていますので、以下に2022/4時点で最新のプラグイン(1.24.1)をベースに記載した記事を投稿していますので、こちらを参照願います。 aburi6800.hatenablog.com

目次

Obsidian gitプラグインとは

  • ObsidianでGitリポジトリへのpush、pullを行えるサードパーティープラグイン
  • Githubなどのリモートリポジトリを介して複数端末からファイルを編集できるようになるので便利。(リモートリポジトリがバックアップにもなる)
  • 事前にGitのリモートリポジトリの作成とSSH接続の設定を行っておく必要があるので、若干敷居が高い。

    GithubとのSSH接続に関しては、Web上に解説記事があるので、そちらを参照ください。(気が向いたらこのブログにも書きます)

使用方法

Obsidianがインストールされている前提で、以下を行う。

  1. サードパーティープラグインから、Obisdian Gitプラグインをインストールする。
  2. 「別の保管庫を開く」から、事前にcloneしておいたディレクトリを保管庫として開く。
  3. 設定を行う。

設定

f:id:aburi6800:20210206235504p:plain
Obsidian gitの設定画面

  1. Valut Backup Interval (minutes):自動的にリモートリポジトリにpushする間隔を分で指定する。
  2. Commit message:リモートリポジトリへのコミットメッセージを指定する。{{date}}のフォーマットは次で指定可能。
  3. {{date}} placeholder format:コミットメッセージの{{date}}の書式を設定する。
  4. Preview commit message:上記で設定したコミットメッセージをプレビューする。
  5. Current branch:push先のブランチを指定する。(通常はmasterのままでOK)
  6. Pull updates on startup:Obsidian起動時にリモートリポジトリからpullするかを設定する。(ON推奨)
  7. Disable push:リモートリポジトリへのpushを禁止する。(通常はOFFでOK)
  8. Disable Notifications:通知表示をOFFにする。(右上の表示が鬱陶しい場合はONにする)

コマンド

ホットキーは初期設定時は割当なし。 以下は私の(今のところの)設定例です。

コマンド 説明 ホットキー
Obsidian git: Pull from remote repository リモートリポジトリからpullする [Ctrl]+[Shift]+[P]
Obsidian git: Commit all changes and push to remote repository 全ての変更ファイルをcommitし、リモートリポジトリにpushする [Ctrl]+[Shift]+[Enter]

※作業終了時に必ず[Ctrl]+[Shift]+[Enter]を叩く癖をつける

注意点

  • 競合が発生した場合、マージは手動で行う必要があるので注意。(=チームでの共同作業には向かないかも知れない)
  • .obsidian/workspace は各端末での画面の状態を保存しているファイルなので、.gitignoreに登録推奨。

総括

このプラグイン、説明ページにも使用方法が明確に書かれていなかったり(私が見落としただけ?)、機能が最低限のため競合が発生すると別手段での解消が必要になったりと、少々不親切な部分がありますが、1台のみでObsidianを使っていたとしてもGitリポジトリにバックアップが取れるので、Obsidianを使用しているなら試してみる価値はあります。

他にDropboxやOneDrive経由で同期する手段もありますが、Dropboxは無料アカウントでの制約が厳しくなったし、Linuxも併用するならGitが一番汎用的で使いやすいのではないでしょうか。

なお、スマートフォン/タブレットでは、AndroidではGitJournalというアプリで、Githubのリモートリポジトリへの接続やマークダウン形式のドキュメントを扱うことができます。(iPhoneは色々とあるようですが、持っていないので未調査)

なお、Obsidianのモバイルアプリが開発中、2021年中にリリースされる予定とのことなので、期待しています。


参考:
Obsidian
Github - Obsidian git

【VSCode】importで未解決の警告(import ~ could not be resolved)が出る問題の対策方法

 VisualStudioCodeを使ってPythonのソースを書くときに便利な拡張機能として、Pylanceがリリースされておりますが、ちょっとハマったことがあったので、記録として記事にしておきます。
 似たような事象に悩まされている方のお役に立てれば幸いです。
 

発生した事象

 今回作ったワークスペースは、以下のようなディレクトリ構成としました。

${WorkSpaceFolder}
└ source
 ├ module
 │ └ myModule.py
 └ main.py

 
 main.pyでは、以下のようにmoduleパッケージにあるユーザーモジュールmyModule.pyhogeクラスをimportしようとしています。

from .module.myModule import hoge

 
 すると、以下の警告が発生しました。

ImportError: attempted relative import with no known parent package

 
 なお、警告が出るだけで、実行は可能な状態です。(Python上では問題ない)
 

対処

 以下の手順で、先ほどの警告が出なくなるはずです。
(設定値のフォルダ名は合わせて適宜変更、複数ある場合はカンマで続けて記載していく)
 
① ワークスペースのルートディレクトリにある.vscode/ディレクトリにsettings.jsonファイルを作成し、以下の内容を記述する。

{
    "python.analysis.extraPaths": [
        "./source"
    ]
}

VSCodeの設定から拡張機能のPylanceを選択し、ワークスペースタブのPython > Analsys: Extra Pathsの「項目の追加」を押して、設定値を記載しても同じです。
※パスはワークスペースディレクトリからの相対パスで指定してください。${WorkSpaceFolder}/sourceのように絶対パスで指定するとうまくいかないようです。
 
② VSCodeを再起動する。
 
③ import文を以下のように修正する。

from module.myModule import hoge

※パッケージの先頭にあった「.」(ピリオド)を削除する
 

原因

 Pylanceでは、通常はワークスペースのルートディレクトリ(VSCodeで開いたディレクトリ)をユーザーモジュールのインポートのメインルートとして扱います。
 それ以外のディレクトリをユーザーモジュールのメインルートとした場合は、Pylanceは見つけることができないため、警告が出ていたということでした。
 なので、以下のようなディレクトリ構成だった場合は、特に対応する必要はなかったわけです。

${WorkSpaceFolder}
├ module
│ └ myModule.py
└ main.py

 

さいごに

 この事象の説明と対応は、以下のURL(PylanceのGitHubリポジトリにある、Trubleshootingドキュメント)にも記載がありますので、参考に貼っておきます。
https://github.com/microsoft/pylance-release/blob/master/TROUBLESHOOTING.md#unresolved-import-warnings  
 また、調査に協力いただいたHiromasaさん、本当にありがとうございました!

【Python】Pyxelでカナ文字を表示したい

 さて今回は、Pythonゲームエンジン「Pyxel」を使ったカナ文字表示処理を作った記事です。
 ちょっと長文になりますが、作成の過程を赤裸々に書いていますので、何かの参考になれば幸いです。
 
 なお、ここではPythonやPyxelのインストール等の細かい説明は割愛します。
 Pyxelについては、公開されているGitHubリポジトリにあるREADMEを参照してください。
(今後も更新される可能性があるため、他のサイトの記事よりも、こちらを参照することを推奨します)

github.com

事の経緯

 さて、Pyxelでゲームを作り始めたところ、標準のAPIでは英文字(大文字・小文字)と数字、記号の表示はできますが、カタカナ・ひらがなの表示ができませんでした。
 メッセージが重要でないゲームであればこのままで良いのですが、今作っているのはRPGで、メッセージを読ませるためにカナ文字を表示したいのです。
 そこで、自前でカナ文字の表示を実現しよう、というのが始まりでした。

まずは文字画像の素材から

 一言に「カナ文字を表示」と言っても、プログラムとしてやることは「文字の形に作った画像を表示する」です。
 普通のキャラクターの表示と変わりません。
 なので、まずは文字パターンの画像データを作りました。

f:id:aburi6800:20200613132801p:plain
ひらがな・カタカナの画像データ
 Pyxelの英数字は、3x6ドットと縮小されたものですので、これと合わせたときに違和感がないように、7x6ドットで書いています。
(さすがにこれ以上小さくするのは無理でした・・・(^^;)
 これを、一般的なドットエディタで256x256ドットのpngで保存して、Pyxelに同梱されているPyxelEditorに先ほど作った画像を取り込んでリソースデータにします。

最初の実装

 次に、プログラムで表示していきます。
 まずは、どのように実装するかですが、以下のnote記事を参考にさせていただきました。

note.com

 簡単に説明すると、Pythonの辞書型に、「あ」だったら「A」のようにキーとする文字と、文字パターン画像データの対応する座標を定義していくものです。
 具体的には、以下のようなコードになります。
 キーは、ひらがなは大文字(「A」、「SA」など)、カタカナは小文字(「a」、「sa」など)としました。
 長すぎるので一部抜粋していますが、これをすべての文字に対して定義します。

# -*- coding: utf-8 -*-
import pyxel

class PyxelUtil:

    KANA_DIC = {
        "A"  : [  0,   1],
        "I"  : [  8,   1],
        "U"  : [ 16,   1],
        "E"  : [ 24,   1],
        "O"  : [ 32,   1],
    :
    :
(以下、定義が続く)

 
 これを実際に表示する処理として、以下のようなコードを書きました。

    @staticmethod
    def text(x, y, txt, color=7):  

        for i in range(len(txt)):
            if txt[i][0] == "*":
                t = txt[i].replace("*", "")
                pyxel.text(x, y, t, color)
                x = x + 4 * len(t)
            
            else:
                font_xy = PyxelUtil.KANA_DIC[txt[i]]
                fontx = font_xy[0]
                fonty = font_xy[1]
                pyxel.pal(7, color)
                pyxel.blt(x, y - 1, 0, fontx, fonty, 7, 6, 0)

 
 Pyxelのtextメソッドと同じ名前にして、引数も同じ構成にしています。
 ただし、引数の「txt」はリストの形としており、カナ文字の場合は辞書のインデックスを指定します。
 英数字を含んだメッセージを表示したかったので、先頭に「*」を付けた要素はPyxelのtextメソッドでそのまま表示するようにしました。
 
 これを使って実際に文字表示をするコードは、次のようになります。

PyxelUtil.text( 16,  16, ["A", "I", "U", "E", "O", "*ABCDE12345"], 7)

問題その①

 さて、ここまでで想定した通りに文字が表示ができるようになりました。
 しかし、この処理構成には、大きな問題があります。
 「Pyxelのイメージバンクの一部を文字パターン画像データで消費している」という問題です。
 このため、他のプログラムでは流用しにくいものになっているのです。
 例えば、イメージバンクのすべてを使い切ったゲームの場合は、この文字パターン画像データをイメージバンクに入れることができないので、カナ文字を表示できないことになります。
 
 そこで、文字パターンデータをプログラムに直接持とう、と考えました。
 そこでまず、ドットのあるところは1、ないところは0として、次にように全てデータ化しました。(大変でした・・・)

    KANA_DIC = {
        "A"  : ["0010000", "0111100", "0010001", "1111110", "1010101", "0111001"],
        "I"  : ["0000000", "1000010", "1000001", "1010001", "0110001", "0010000"],
        "U"  : ["0011100", "0000000", "0111110", "1000001", "0000001", "0011110"],
        "E"  : ["0011110", "0000000", "1111111", "0000110", "0111000", "1100111"],
        "O"  : ["0001001", "0111101", "0001000", "0111110", "1001001", "0111001"],
    :
    :
(以下、定義が続く)

 
 次に表示ですが、システム用イメージバンクの表示スクリーン用に直接描いてはどうだろうか?、と考えました。
 そこで、以下のようにしました。

    @staticmethod
    def text(x, y, txt, color=7):  

        for idx in range(len(txt)):
            if txt[i][0] == "*":
                t = txt[i].replace("*", "")
                pyxel.text(x, y, t, color)
                x = x + 4 * len(t)
            
            else:
                data = copy.deepcopy(PyxelUtil.KANA_DIC[txt[idx]])
                for i in range(len(data)):
                    data[i] = data[i].replace("1", "{:1x}".format(color))                    
                pyxel.image(4, system = True).set(x, y, data)
                x = x + 8

 
 else以降が修正した部分です。
 ビットパターンに色を指定するために一時的に辞書のデータを書き換えるので、copyモジュールをimportしてdeepcopyしました。
(単純にdata = PyxelUtil.KANA_DIC[txt[idx]]とすると、元の辞書データも変更されてしまいました。Pythonのリストは参照渡しのようです。)  これをsetメソッドで、表示スクリーン用イメージに直接描画しています。
 
 これで試したところ、画面に直接、プログラムで定義したパターンが描画することができました。
(システムリソースにアクセスするための引数の書き方を、最初はimage(4, True)としていて動かず、しばらく悩みましたが・・・)
 
 ようやく汎用的な文字表示クラスが作れた!
 これでどんなゲームでもカナ文字が表示できる!
 と、喜んでいたのですが・・・

問題その②

 先の対応で、Pyxelのリソースデータを消費せずに、カナ文字を含めた文字表示ができるようになりました。
 しかし、色々試していたところ、Pyxelのtextメソッドとは描画される挙動がちょっと違っていたのです。
 
 たとえば、pyxel.textを使って文字を重ねて表示すると、次のような結果になります。

f:id:aburi6800:20200613122835p:plain
pyxelのtextメソッドで重ねて表示
 次に、今回作成したtextメソッドで文字を重ねて表示すると、次のような結果になりました。
f:id:aburi6800:20200613123145p:plain
今回作成したtextメソッドで重ねて表示
 
 このように、「背景色の透過がされていない」んですね。
 それもそのはず、データ上「0」とした部分は、透明ではなく黒ですからね・・・。
 この黒の部分も含めてすべてのデータを表示スクリーン用イメージに描いているのですから、透過されるわけがありません。
 Pyxelのbltメソッドで透過表示できるのは、恐らく内部的には、イメージバンクから表示スクリーン用イメージにコピーするときに指定された色を描画しないようにしているのだと考えます。
 
 であれば、データで「0」とした部分を描かず、「1」となっている部分に対して、指定された色でドットを打てば良いですね。
 Pyxelではpsetメソッドが用意されていますので、これを使います。
 psetメソッドは表示スクリーン用イメージに直接描画するようですので、何も考えずに以下のようなコードとしました。

    @staticmethod
    def text(x, y, txt, color=7):  

        for idx in range(len(txt)):
            if txt[idx][0] == "*":
                t = txt[idx][1:len(txt[idx])]
                pyxel.text(x, y + 1, t, color)
                x = x + 4 * len(t)
            
            else:
                try:
                    for row, data in enumerate(PyxelUtil.KANA_DIC[txt[idx]]):
                        for col in range(len(data)):
                            if data[col] == "1":
                                pyxel.pset(x + col, y + row, color)
                    x = x + 8
                except KeyError:
                    x = x + 8
                    continue

 
 for文を2重にし、外側は辞書の各文字に定義したビットパターンの配列に対してのループ、内側は各配列要素に対してのループになります。
 要するに、全部のビットパターンを精査して、「1」だったら指定された色でpsetする、という処理です。

 tryexceptで括ったのは、辞書に存在しないキーが指定されたときにエラーで止まらず空白を開けるようにするためです。(元から潜在していた不具合ですね・・・)
 また、単純に「*」をreplaceしていたため、文字としての「*」が表示できない不具合がありましたので、5~6行目も修正しています。
 
 では、実行してみます。

f:id:aburi6800:20200613130233p:plain
修正したtextメソッドで重ねて表示
 
 おおっ!無事に、カナ文字も重ねて表示できるようになりました!!
 処理速度的にも問題なさそうです。

最後に

 実際にわかってしまえば「なんでこんな処理に苦労したんだ?」となるような処理ですが、ここに辿り着くまでに苦労したんですよ・・・。
 おかげで、だいぶPythonもPyxelもわかってきました。
 
 なお、今回作ったモジュールは、以下のGithubリポジトリに公開しています。

github.com

 cloneしたらpyxelUtil.pyをコピーしてきて、importしたらそのまま使えます。
 使い方は、pyxelUtil.pyを実行するか、プログラムソースの後半を見ればすぐにわかるかと思います。
 また、必要最低限のパターンしか定義していませんが、記号などの追加も簡単にできるように、単純なソースにしています。
 ご自由にお使いいただければと思います。

【Python】3D迷路の中を歩きたい②

 前回は、現在の位置と方向を判断して、マップから表示に必要な情報を取得するところまで行いました。
 今回は、実際に画面へ表示するための考え方になります。
 なお、描画の方法(アルゴリズム)はいくつかありますが、ここでは一番簡単な手法を記載します。

実際に画面に描画する前に

 画面に描画する前に、まず、画面の座標系は以下と仮定します。

f:id:aburi6800:20200609001751p:plain
画面の座標

 ここに、奥から手前に向かって壁の絵を書いて行けば良い訳です。
 その前に、前回はマップの情報を取得する順番を以下のようにしていました。

 |0|1|2|3|4|
   |5|6|7|
   |8|9|A|
   |B|C|D|

 これだと処理の都合が悪いので、以下のように正面を最後にするように変更します。

 |0|1|4|3|2|
   |5|7|6|
   |8|A|9|
   |B|D|C|

 この順でのマップ参照先の定義は、以下のようになります。

# マップの参照先の定義
# 参照順のイメージは以下(上向きの場合。自分の位置はDとする)
# |0|1|4|3|2|
#   |5|7|6|
#   |8|A|9|
#   |B|D|C|
POS_X = (
    (-2,-1, 2, 1, 0,-1, 1, 0,-1, 1, 0,-1, 1, 0),
    ( 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0),
    ( 2, 1,-2,-1, 0, 1,-1, 0, 1,-1, 0, 1,-1, 0),
    (-3,-3,-3,-3,-3,-2,-2,-2,-1,-1,-1, 0, 0, 0)
)
POS_Y = (
    (-3,-3,-3,-3,-3,-2,-2,-2,-1,-1,-1, 0, 0, 0),
    (-2,-1, 2, 1, 0,-1, 1, 0,-1, 1, 0,-1, 1, 0),
    ( 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0),
    ( 2, 1,-2,-1, 0, 1,-1, 0, 1,-1, 0, 1,-1, 0)
)

一番奥の壁を描く

 さて、前置きが長くなりました。
 まずは一番奥の壁から描きます。
 壁の描画イメージは、次の図のようになります。

f:id:aburi6800:20200609003602p:plain
一番奥の壁のイメージ
 まずは0番目、一番左奥が壁だった時は、次のような描画をします。
f:id:aburi6800:20200609003331p:plain
0番目が壁だった時の描画
 何となく立体に見えればOKです。
 
 次に、その一つ右が壁だった時は、以下を上書きで描画します。
f:id:aburi6800:20200609003813p:plain
1番目が壁だった時の描画
 その結果、一番左の壁のサイドの部分が無くなり、以下のような画面になるでしょう。
f:id:aburi6800:20200609004031p:plain
0番目と1番目が壁だった時の描画
 これと同様に右側も描いていき、最後に正面の壁を描けば終わりです。(正面の壁は、真四角を描くだけですね)

残りの壁を描く

 残りの壁も、同じ考え方で上書きしていきます。
 ただ、一番奥と違うのは、自分の正面とその左右のみ描くという点です。
 また、壁のサイドの部分は、奥の壁と不自然につながらないように気を付ける必要がありますので、座標の計算には注意してください。
 
 参考として、一番奥から一つ手前の壁を描くイメージは、次のようになります。

f:id:aburi6800:20200609014025p:plain
一番奥から一つ手前の壁のイメージ
 例えば5番目が壁だった時は、次のように描画します。
f:id:aburi6800:20200609010955p:plain
5番目が壁だった時の描画
 先ほどの一番奥の描画に、これを上書きした結果は、以下のようになるはずです。
f:id:aburi6800:20200609011103p:plain
1,2,5番目が壁だった時の描画
 
 また、自分の直前の壁を描くイメージは、次のようになります。
f:id:aburi6800:20200609011220p:plain
直前の壁の描画順
 8番目が壁だったら、以下のようになりますね。
f:id:aburi6800:20200609011525p:plain
8番目が壁だった時の描画
 
 最後に、自分の左右が壁だった場合です。
 ここだけちょっと特殊で、まずイメージは以下になります。
f:id:aburi6800:20200609011727p:plain
自分の左右が壁だった時のイメージ
 通常は壁が通り抜けられないので、自分の位置が壁になることはありません。そのため、D番目は無視します。
 さて、自分の左右が壁の場合ですが、それぞれのサイド部分だけ描画します。
 例えば、B番目が壁だった場合は、次のようにします。
f:id:aburi6800:20200609012238p:plain
B番目が壁だった時の描画

最後に

 
 結局のところは、前の記事に書いた、どうやってデータを拾うかと、データが処理しやすい順番に拾えるか、が全てになります。
 それさえクリアしてしまえば、画面に表示すること自体は単純になることがおわかり頂けたかと思います。
 
 ちなみに、「3D迷路を歩きたい」というタイトルなのに移動処理に触れていないのは、移動すること自体、それほど難しくないためです。
(ヒントも前の記事に描いています)
 実際にキー入力から移動する処理を組んでみて、迷路を歩けるか試してみるのも面白いと思います。

【Python】3D迷路の中を歩きたい①

 久しぶりの更新です。
 いきなりですが、今回はPythonで3D迷路をやってみたいと思います。

(2020/6/1追記)
 実装を進めたところ、マップの検索範囲について問題があったため、修正しました。

なぜ3D迷路?

 私にとって3D迷路は、古くはWizardryからの憧れでした。
 あの暗く無機質なダンジョンを黙々と歩くだけで、とてもスリルに満ちたものでした。
 あまりにも面白いので、自作RPGでも3Dダンジョンを探索するもの作りたかったのです。
 しかし、昔の自分は3D迷路を表示するアルゴリズムがどうしても思いつかず、実現できませんでした。

 そして今、改めて実現方法を考えていたところ、うまく行きそうな感触が得られました。
 今となっては不要な情報なのかも知れませんが、一応参考として作成過程を残しておこうと思います。

なぜPython

 これまでの記事を見た方は、せっかくMSXを始めたのに何故?と思われるかも知れませんが・・・。
 正直、アセンブラは処理の組み方が難しく、なかなか形にできませんでした。
 そこで、昔のBASICのような感覚で、コンパイルなしに簡単に動かすことができて、OSにあまり依存しない言語はないか?と探していたところ、Pythonに行きついた、という訳です。
 ゲームの分野ではまだマイナーですが、Tcl/TkやゲームエンジンPygame、Pyxel)を使うことで作ることも可能です。
 言語的にもC++C#など他の言語よりシンプルなので、Sandbox的に使うのもありかと思います。

まずは下準備

 さて、3D迷路でよくある操作系では、上を押すと前進、左右で方向を変える、というものがあります。
 これは、普通の見下ろし型マップと違い、明確に方向を意識する必要がある、ということですね。
 この方向の概念を処理しやすくするため、まずは以下の定数(といっても、Pythonには定数がないので形だけですが)を定義します。

# 方向
DIRECTION_NORTH = 0
DIRECTION_EAST = 1
DIRECTION_SOUTH = 2
DIRECTION_WEST = 3

 時計回りに北方向、東方向、南方向、西方向としています。
 こうすると、方向を表す変数を加減算することで、方向を変えられます。
 
 次に、それぞれの方向に対する、横座標、縦座標の移動量を定義します。
 マップデータは2次元のリストで作るのですが、その中の自分の位置を変えるときの、方向に対する移動量というわけです。
 ゲーム中は変更する必要がないのでタプル型にし、方向の値に対応したインデックスに横方向、縦方向の移動量を設定します。

# 方向に対する増分
vx = ( 0, 1, 0,-1)
vy = (-1, 0, 1, 0)

 例えば、上方向の場合は北方向を表す定数の値がゼロなので、vx[0]とvy[0]の値が参照され、X座標は変更がなく、Y座標は-1されます。
 左方向の場合は西方向を表す定数の値が3なので、vx[3]とvy[3]の値が参照され、X座標は-1され、Y座標は変更がありません。

画面の表示に必要なマップの情報を得る

 さて、ここまででコントローラーの左右を押して方向を変え、上を押して前進するための準備はできました。
 次に、迷路を表示するため、自分の正面方向に見えるマップの情報を取得するためのデータを準備をします。
 あまり遠くまで見通せるとダンジョンの暗い雰囲気が出ないので(実は処理が面倒なだけですが)、ここでは自分から見て2ブロック先まで見えるようにします。
 これを実現するため、以下の範囲でマップを見ることにします。

 |0|1|2|3|4|
   |5|6|7|
   |8|9|A|
   |B|C|D|

 この図は、自分が北方向を見ているとした場合に画面に見える範囲です。
 この順番でマップの2次元リストからデータを取得します。
 Cは自分の位置としており、B~A(A,B,C,Dは10,11,12,13と読み替えてください)は自分とその左右のブロック、8~Aは1ブロック先(目の前)とその左右、5~7が2ブロック先、0~4は3ブロック先となります。
 3ブロック先を5ブロック見るのは、例えば目の前に壁がないときなどで画面の情報量を増やすためです。
 遠くにあるものは多くのものが見える、というわけですね。
 
 取得先のマップの座標は、上の図の場合、以下のようになります。

 |(x-2,y-3)|(x-1,y-3)|(x,y-3)|(x+1,y-3)|(x+2,y-3)|
           |(x-1,y-2)|(x,y-2)|(x+1,y-2)|
           |(x-1,y-1)|(x,y-1)|(x+1,y-1)|
           |(x-1,y)  |(x,y)  |(x+1,y)  |

 この座標増分データを、4方向それぞれに対して用意してやれば良いわけです。
 具体的には、以下のように2次元のタプル型で定義します。

# マップの参照先の定義
# 参照順のイメージは以下(上向きの場合。自分の位置はCとする)
# |0|1|2|3|4|
#   |5|6|7|
#   |8|9|A|
#   |B|C|D|
pos_x = (
    (-2,-1, 2, 1, 0,-1, 1, 0,-1, 1, 0,-1, 1, 0),
    ( 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0),
    ( 2, 1,-2,-1, 0, 1,-1, 0, 1,-1, 0, 1,-1, 0),
    (-3,-3,-3,-3,-3,-2,-2,-2,-1,-1,-1, 0, 0, 0)
)
pos_y = (
    (-3,-3,-3,-3,-3,-2,-2,-2,-1,-1,-1, 0, 0, 0),
    (-2,-1, 2, 1, 0,-1, 1, 0,-1, 1, 0,-1, 1, 0),
    ( 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0),
    ( 2, 1,-2,-1, 0, 1,-1, 0, 1,-1, 0, 1,-1, 0)
)

 ちょっとややこしいですが、pos_x、pos_yは自分の現在の座標に対する、横・縦座標の増減量で、それぞれ4方向に対して定義しています。
 インデックス0は先の図と合わせて見て頂けるとわかると思います。
 インデックス1は先の図を右回りに90度回転させたイメージで定義しています。
 インデックス2、3はインデックス0、1の値の正負を逆転すれば良いだけですね。

マップデータの取得をテストしてみる

 さて、ここまでで移動に関する準備と表示に関する準備ができました。
 画面表示の処理に取り掛かる前に、想定した通りにマップデータを取得できているか、確認してみます。
 
 まず、自分の今の位置と方向を定義します。
 マップの左上に、下向きに位置している設定とします。

# 自分の最初の座標と方向
x = 1
y = 1
direction = DIRECTION_SOUTH

 次に、適当なマップデータを作ります。
 外周は必ず壁で囲みます。  まだ歩き回れないので、小さなものにします。

# マップ
# 0 = 通路
# 1 = 壁
# 外周は必ず壁とする
map = [
    [ 1, 1, 1, 1, 1],
    [ 1, 0, 1, 0, 1],
    [ 1, 0, 1, 0, 1],
    [ 1, 0, 0, 0, 1],
    [ 1, 1, 1, 1, 1]
]

 最後に、マップから情報を取得する処理を書きます。
 pos_x、pos_yの要素数だけ繰り返しますが、2ブロック先はマップ外を参照する可能性があるため、マップ外を見ようとしたときは壁として処理するようにします。

for i in range(0, 14):
    map_x = x + pos_x[direction][i]
    map_y = y + pos_y[direction][i]
    if map_x < 0 or map_x > 4 or map_y < 0 or map_y > 4:
        data = "1"
    else:
        data = map[map_y][map_x]

    print(str(map_x) + ":" + str(map_y) + "=" + str(data))

実行すると、以下のようにコンソールに出力されました。

3:4=1
2:4=1
-1:4=1
0:4=1
1:4=1
2:3=0
0:3=1
1:3=0
2:2=1
0:2=1
1:2=0
2:1=1
0:1=1
1:1=0

 うまく取得できているようです!
 あとは、この順番に「1」の部分で距離に対応した壁の絵を書けば、3D迷路が表示できるはずです。

【雑記】延々と出続ける「ハングアウトのSMSのサポートは終了しました」という通知に対する対応

 タブレット購入後にずっと「smsのサポートは終了しました」という通知が表示されている。
 これは固定されており、消そうにも消せない。
 通知をタップするとPlayストアに繋がり、SMSアプリの一覧が表示されるが、このタブレットWifiモデルなのでSMSを使用できない。
 そうこうしているうちに通知は消えるのだが、忘れたころに再び表示される。
 さて困った(というより、鬱陶しい)、と色々調べていると、次のような理由であることがわかったので記録。
 

 まず、この現象は、SMSアプリにハングアウトが指定されているのがそもそもの原因。
 恐らく、このタブレットが発売された時点ではハングアウトにSMS機能がサポートされていたが、これが廃止になったことでこのような事態を招いているのであった。
 くそっ、googleめ・・・。
 

 で、これをどうにかして無効にしたい。
 まず「OSの設定」→「アプリと通知」を開く。

f:id:aburi6800:20190815001015p:plain

 次に「詳細設定」。

f:id:aburi6800:20190815001241p:plain

 そして「デフォルトアプリ」をタップ後、「SMSアプリ」をタップ。

f:id:aburi6800:20190815001515p:plain

 すると・・・

f:id:aburi6800:20190815001715p:plain

 おぅふ。
 無効どころか他のアプリも選択できないのですね。
 たしか、facebookのMessengerは入っていたはずなのだが・・・。
 

 なので、どうせ使えないSMSなので、通知自体を無効にしましたとさ。
 ちゃんちゃん。
 

 SIM対応端末の場合は、ここに認識されそうな適当なSMSアプリを入れ、SMSのデフォルトアプリに指定することで変な通知は出なくなるはずです。
 

 いやー、Androidって難しいっすね!(嫌味)