がるの健忘録

エンジニアでゲーマーで講師で占い師なおいちゃんのブログです。

3Dダンジョンを作ってみたい

front系苦手なのですがまぁ「3Dダンジョン作りたいなぁ」という欲求は以前からガチガチに強めなので(笑
とりあえず、試行錯誤を記録してみようかなぁ、と。

一応想定としては
・一端、JavaScriptcanvasワイヤーフレームの3Dダンジョンを作る
あたりが一端のゴールでふ。

んで。
とりあえず「3Dダンジョン書く」のに、マップデータは用意するとして、それを「読み込んで」「ワイヤー書く」必要があるので。
今回はまず「読み込む」あたりを。

f:id:gallu:20210611232754p:plain
こんな感じで、●が自分の位置想定で「n歩先」まで見れると仮定して、こんなxyの座標になると思うのです。
で「これ、計算できないかなぁ?」と思って、ちょりっと思考錯誤して出来るようになったので、メモかてがて。
調査込みなので「本当は関数なりメソッドなりにする」んだけど、その手前のコードをそのまま載せます。

<?php
declare(strict_types=1);
error_reporting(-1);

//
$data = [];

// 迷路で「どのマスまで見るか」
// 配列は[x軸, y軸]
for($i = 1; $i < 4; ++$i) {
    //
    $awk = [];
    // 0ので重複が出るから「一端keyに入れる」「後でkeysで取り出す」にする
/*
    // x軸
    for($x = ($i * -1); $x <= $i; ++$x) {
        $awk["{$x}, {$i}"] = true;
    }
    // y軸
    $x = $i;
    $xx = $i * -1;
    for($y = 0; $y <= $i; ++$y) {
        $awk["{$x}, {$y}"] = true;
        $awk["{$xx}, {$y}"] = true;
    }
*/
    // loopをまとめて
    for($z = 0; $z <= $i; ++$z) {
        // x軸
        $x = $z;
        $xx = $z * -1;
        $awk["{$x}, {$i}"] = true;
        $awk["{$xx}, {$i}"] = true;
        // y軸
        $x = $i;
        $xx = $i * -1;
        $y = $z;
        $awk["{$x}, {$y}"] = true;
        $awk["{$xx}, {$y}"] = true;
    }

    //
    $awk = array_keys($awk);
    sort($awk); // 見やすいように一端
    $data[$i] = $awk;
}
var_dump($data);

さて次は「Mapデータの持ち方」と、そしたら「レンダリング」かなぁ。

MySQLでUNIQUEに引っかかった時のAUTO INCREMENTの挙動

ちとうちの子から

MySQLでユニークキーとPKあるテーブルに対してON DUPLICATE KEY UPDATE張って、uniqueでの重複させるとauto increment値飛んじゃうんだね。。。

という興味深い話を聞いたので、早速実験。

CREATE TABLE test (
    id SERIAL,
    name VARCHAR(256) UNIQUE,
    PRIMARY KEY(id)
);

mysql> insert into test(name) values('n1');
Query OK, 1 row affected (0.02 sec)

mysql> select * from test;
+----+------+
| id | name |
+----+------+
|  1 | n1   |
+----+------+
1 row in set (0.00 sec)

うんまぁ、ここまでは前提。

んでわ、確認。

mysql> insert into test(name) values('n1');
ERROR 1062 (23000): Duplicate entry 'n1' for key 'name'

mysql> INSERT INTO test (name) values ('n2') ON DUPLICATE KEY UPDATE name='n3';
Query OK, 1 row affected (0.02 sec)

mysql> select * from test;
+----+------+
| id | name |
+----+------+
|  1 | n1   |
|  3 | n2   |
+----+------+
2 rows in set (0.00 sec)

……あ、ほんどだ飛んでる。

mysql> INSERT INTO test (name) values ('n3') ON DUPLICATE KEY UPDATE name='n4';
Query OK, 1 row affected (0.02 sec)

mysql> select * from test;
+----+------+
| id | name |
+----+------+
|  1 | n1   |
|  3 | n2   |
|  4 | n3   |
+----+------+
3 rows in set (0.00 sec)

……続けると飛んでないなぁ。

mysql> insert into test(name) values('n1');
ERROR 1062 (23000): Duplicate entry 'n1' for key 'name'
mysql> INSERT INTO test (name) values ('n4') ON DUPLICATE KEY UPDATE name='n5';
Query OK, 1 row affected (0.01 sec)

mysql> select * from test;
+----+------+
| id | name |
+----+------+
|  1 | n1   |
|  3 | n2   |
|  4 | n3   |
|  6 | n4   |
+----+------+
4 rows in set (0.00 sec)

うん、エラーが起きると飛ぶ………はて?

mysql> insert into test(name) values('n1');
ERROR 1062 (23000): Duplicate entry 'n1' for key 'name'
mysql> insert into test(name) values('n1');
ERROR 1062 (23000): Duplicate entry 'n1' for key 'name'
mysql> insert into test(name) values('n1');
ERROR 1062 (23000): Duplicate entry 'n1' for key 'name'
mysql> INSERT INTO test (name) values ('n5') ON DUPLICATE KEY UPDATE name='n6';
Query OK, 1 row affected (0.02 sec)

mysql> select * from test;
+----+------+
| id | name |
+----+------+
|  1 | n1   |
|  3 | n2   |
|  4 | n3   |
|  6 | n4   |
| 10 | n5   |
+----+------+
5 rows in set (0.00 sec)

あ、回数分とんだw

mysql> insert into test(name) values('n1');
ERROR 1062 (23000): Duplicate entry 'n1' for key 'name'
mysql> insert into test(name) values('n1');
ERROR 1062 (23000): Duplicate entry 'n1' for key 'name'
mysql> insert into test(name) values('n6');
Query OK, 1 row affected (0.02 sec)

mysql> INSERT INTO test (name) values ('n7') ON DUPLICATE KEY UPDATE name='n8';
Query OK, 1 row affected (0.02 sec)

mysql> select * from test;
+----+------+
| id | name |
+----+------+
|  1 | n1   |
|  3 | n2   |
|  4 | n3   |
|  6 | n4   |
| 10 | n5   |
| 13 | n6   |
| 14 | n7   |
+----+------+
7 rows in set (0.00 sec)

うん「UNIQUEでエラーするとエラー回数分飛んで、一回insertするとリセットされる」ぽい。

mysql> insert into test(name) values('n1');
ERROR 1062 (23000): Duplicate entry 'n1' for key 'name'
mysql> insert into test(name) values('n1');
ERROR 1062 (23000): Duplicate entry 'n1' for key 'name'
mysql> insert into test(name) values('n1');
ERROR 1062 (23000): Duplicate entry 'n1' for key 'name'
mysql> insert into test(name) values('n1');
ERROR 1062 (23000): Duplicate entry 'n1' for key 'name'
mysql> insert into test(name) values('n1');
ERROR 1062 (23000): Duplicate entry 'n1' for key 'name'
mysql> insert into test(name) values('n8');
Query OK, 1 row affected (0.01 sec)

mysql> select * from test;
+----+------+
| id | name |
+----+------+
|  1 | n1   |
|  3 | n2   |
|  4 | n3   |
|  6 | n4   |
| 10 | n5   |
| 13 | n6   |
| 14 | n7   |
| 20 | n8   |
+----+------+
8 rows in set (0.00 sec)

しつこく確認&別に「ON DUPLICATE KEY UPDATE」じゃなくても、飛ぶねぇ……


……あれ? じゃぁ、PKの時にも飛ぶのん?

mysql> drop table test;
Query OK, 0 rows affected (0.07 sec)

mysql> CREATE TABLE test (
    ->     id SERIAL,
    ->     name VARCHAR(256) UNIQUE,
    ->     PRIMARY KEY(id)
    -> );
Query OK, 0 rows affected (0.28 sec)

mysql> insert into test(name) values('n1');
Query OK, 1 row affected (0.02 sec)

mysql> insert into test(name) values('n2');
Query OK, 1 row affected (0.02 sec)

mysql> select * from test;
+----+------+
| id | name |
+----+------+
|  1 | n1   |
|  2 | n2   |
+----+------+
2 rows in set (0.00 sec)

前提作り直して。

mysql> insert into test(id, name) values(1, 'n3');
ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY'
mysql> insert into test(id, name) values(1, 'n3');
ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY'
mysql> insert into test(id, name) values(1, 'n3');
ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY'
mysql> insert into test(name) values('n3');
Query OK, 1 row affected (0.01 sec)

mysql> select * from test;
+----+------+
| id | name |
+----+------+
|  1 | n1   |
|  2 | n2   |
|  3 | n3   |
+----+------+
3 rows in set (0.00 sec)

ほむ、PKだと飛ばないのか。

mysql> insert into test(name) values('n1');
ERROR 1062 (23000): Duplicate entry 'n1' for key 'name'
mysql> insert into test(name) values('n1');
ERROR 1062 (23000): Duplicate entry 'n1' for key 'name'
mysql> insert into test(name) values('n1');
ERROR 1062 (23000): Duplicate entry 'n1' for key 'name'
mysql> insert into test(name) values('n1');
ERROR 1062 (23000): Duplicate entry 'n1' for key 'name'
mysql> insert into test(name) values('n1');
ERROR 1062 (23000): Duplicate entry 'n1' for key 'name'
mysql> insert into test(name) values('n4');
Query OK, 1 row affected (0.02 sec)

mysql> select * from test;
+----+------+
| id | name |
+----+------+
|  1 | n1   |
|  2 | n2   |
|  3 | n3   |
|  9 | n4   |
+----+------+
4 rows in set (0.00 sec)

UNIQUEで飛ぶのを確認。

ほむ……まぁ「auto increment、そもそも"連番保証"とかしてないし(確か、MariaDB の Galera Cluster だとノード数に応じて飛んだはずだし)」、ってのはあるんですが。
記憶はしておいて損がないネタ、かも、しれない……誰かよかったら「仕組み側」を書いていただけると喜んで読みに行きますwww

「ジャッジをしない(するのが面倒くさい)」という選択

発端は、PHPで割と定期的に見る、こんなコードに起因したお話でございます。

if (!hoge()) {
    処理
}

まぁ関数でも変数でもよいのですが。
この辺を「良し」とする勢と「悪し」とする勢がいる、と認識をしておりまして。
おいちゃんは「良しとはしない勢(悪し勢の弱い目のあたり)」に生息をしております……んですが、「なんで?」ってな辺りのお話でございます。

まぁご大層な理屈とか理念とか理想とかがある、って分けでもございませんで。
端的に片付けると「ジャッジするのが面倒だから」というものぐさ精神の賜物、でございます(笑

もうちょいとかみ砕きまして。

この「ifの中を"論理演算子の否定"だけで書く」のが確実にまずいケースの典型例としては、strpos()があろうか、と思います。
この辺は最近あちこちでとやかく言われていると思われるので、説明は略。

$s = 'abcdef';
if (!strpos($s, 'a')) {
    echo "a is not find.\n";
}

あと、ちょっと思いつかないのですが、戻り値が「string|false」な関数なんかも「文字としての"0"がかえってくる」とアウト風味が満載でございます。

function f() : string|bool {
    return '0';
}

if (!f()) {
    echo "f() is false\n";
}

まぁ「空文字とか空配列とかNULLとか」がfalseになるくらいは、暗黙の変換でも「許容範囲かなぁ」と思わなくもないんですけど、……ねぇ。
この辺の「これの戻り値どうなんだろう?...暗黙の変換どうなるんだろう?」を毎回確認するくらいなら「暗黙の変換やめて明示すりゃよくね?」とか思ってきたりするわけですよ。

書く方は一回だけど、読む方は下手すりゃ毎回だったりしますしねぇ。

この辺の根っこに「決断には、案外とMPがかかる」って認識があるです。
例えば、こちら。
https://www.workport.co.jp/plus/articles/7576

あと、間違ったコードは間違って見えるようにする( 今、キャッシュしかないんですよねぇ…… https://web.archive.org/web/20190317182904/http://local.joelonsoftware.com:80/wiki/%E9%96%93%E9%81%95%E3%81%A3%E3%81%9F%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AF%E9%96%93%E9%81%95%E3%81%A3%E3%81%A6%E8%A6%8B%E3%81%88%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%99%E3%82%8B )的な事も少し想起しています。

なので。
おいちゃんが「if文の中は必ず条件式。論理演算子(否定)を書かない」のは「戻り値がどうなってその結果のbooleanへの暗黙の変換がどうなって...」って考えたり「この戻り値はOKで...この戻り値はNGだから...」とかいう決断をしたりするのが面倒だから、になります(笑

PHPで「型宣言された引数の型の名前」を知る方法 続編

PHPで「型宣言された引数の型の名前」を知る方法 - がるの健忘録 の続編。

いやPHP8になって「union typesとか増えたしなぁ」とか思って検証コード書いてみたので、勿体ないんで、忘れる前に(絶対忘れるだろうしw)、記録しておく。

<?php
namespace Foo;

//
class Bar {
}

// 調査用クラス
class Hoge {
    public function t(\Exception $e, Bar $ar, $s, int|float $i, mixed $m) {
    }
}

/*
//
$ref = new \ReflectionMethod('Hoge', 't');
//
$params = $ref->getParameters();
//
foreach($params as $p) {
    echo $p->getType() , "\n";
}
*/

// 一行にまとめてみるとこんなん
foreach(( new \ReflectionMethod('Foo\\Hoge', 't'))->getParameters() as $p) {
    echo $p->getType() , "\n";
}

結果

Exception
Foo\Bar

int|float
mixed

ほむ、概ね「すげぇ素直」にかえってくるんだ。
クラスは流石に完全修飾クラス名だけど。

NIZの呪文変換

似ザードリィとか呼称されております(笑)「NIZ」の呪文変換のCSVです。
指針としては
・おいちゃん、PC88の頃からやってるので、英字スペル
・サンダーを「MOLITO」とするお話もあるんだけど、記憶が間違ってなければ「MOLITOは1グループ」なので、サンダーはそのまま残す
ってな感じでございます。

# メイジ
スリープ,KATINO
スパーク,HALITO
ウィザードアイ,DUMAPIC
シールド,MOGREF
ダークネス,DILTO
メガシールド,SOPIC
メガファイア,MAHALITO
トータルテラー,MAMORLIS
フリーズ,DALTO
ギガファイア,LAHALITO
サフォケーション,MAKANITO
メガフリーズ,MADALTO
デッドリーエア,LAKANITO
ギガシールド,MASOPIC
シックスブーンズ,HAMAN
ターンアンデッド,ZILWAN
テレポーテーション,MALOR
セブンスブーン,MAHAMAN
ニュークリアブラスト,TILTOWAIT
# プリースト
ヒール,DIOS
パニッシュ,BADIOS
アーマー,KALKI
ブライトネス,MILWA
バックラー,PORFIC
メガアーマー,MATU
ディバイントラップ,CALFO
ロケートパーソン,KANDI
パラライズ,MANIFO
サイレンス,MONTINO
マジックライト,LOMILWA
キュアパラリシス,DIALKO
ネームモンスターズ,LATUMAPIC
ギガアーマー,BAMATU
メガヒール,DIAL
メガパニッシュ,BADIAL
キュアポイズン,LATUMOFIS
プロテクション,MAPORFIC
ギガヒール,DIALMA
ギガパニッシュ,BADIALMA
ファイアウォール,LITOKAN
レイズデッド,DI
デス,BADI
ワールウィンド,LORTO
キュアオール,MADI
ティールライフ,LABADI
エヴァキュエート,LOKTOFEIT
ワードオブカース,MALIIKTO
レイズアッシュ,KADORTO

2021/04/07 18:21 追記
TILTOWAIT と LOKTOFEIT の綴りを間違えていたので修正しました orz
他にミスとかあったら教えてくださいませ ノ

「useを使ってクラスが使えるようになる」までの道程

ちょいとお仕事で、要約すると
・useすればクラス名の解決が可能になるのではないか?
 → 参照したいクラスがあるファイルのnamespace\クラス名をuseで指定すれば使用はできるはず
という趣旨の質問をいただきまして。

これはとても興味深い内容だなぁ、と思ったので「Blogで解説してよい?」と聞いたら快諾いただいたので、早速、ネタに(笑

今回書くのに近しいお話が
namespaceとuse https://note.com/gallu/n/nd4efd1c17e25
autoloadについて https://note.com/gallu/n/n2d0bb7718ceb
requireとinclude https://note.com/gallu/n/n658fbf6fd7ce
に乗っておりますので、よかったら。


結論から書くと
・ use は「別名(エイリアス)の作成をするだけでそれ以外は何もしてくれない」ので「なにもなくてuseだけだとクラスは(多分)使えない」
・クラスの解決には「クラスのオートローディング」という機能を使う
 → 自力で実装してもいいけど、まぁ「既存の実装」があるので、ルールに則りつつそれを使うと楽

となります。
ちょいとこの辺を、昔々のあたりから紐解いてみませう。

以下のクラスがあるとします。

class Hoge
{
}

で、以下のPHPがあった場合、これはHogeクラスを解決できます。

<?php

class Hoge
{
}

//
$obj = new Hoge();

まぁねぇ。
ただこんな風に「1ファイルに全部書く」とか色々と以下略なので、別ファイルに切り出します。

Libs/Hoge.php

<?php

class Hoge
{
}

t.php

<?php
//
$obj = new Hoge();

これだと解決できません。なんでかってぇと「t.phpを実行しても、Hoge.phpを読み込むような記述がどこにもないから」。
古(いにしえ)の術者は、こんな風に解決をしていました。

t.php

<?php
require_once('./Libs/Hoge.php');

//
$obj = new Hoge();

require_onceでもrequireでもinclude_onceでもincludeでもいいんですが(詳しい解説は上述の notes 参照)*1
こんな風に「ファイルを読み込んでやる」と、使えるようになります。
……えぇまぁなので昔は「先頭に100行とか200行とかrequire_onceが書いてある」なんてブツも拝見した記憶がございます。

まぁ「この辺は出来るだけ簡略化したい」と考えるのが優秀なプログラマというものでございます。
上述のnotesの「autoloadについて」を読んでいただくと手っ取り早いのですが。

https://www.php.net/manual/ja/language.oop5.autoload.php

オブジェクト指向アプリケーションを作成する開発者の多くは、 クラス定義毎に一つのPHPソースファイルを作成します。 最大の問題は、各スクリプトの先頭に、必要な読み込みを行う長いリストを 記述する必要があることです(各クラスについて一つ)。
spl_autoload_register() 関数を使うと、 任意の数のオートローダーを登録でき、 クラスやインターフェイスが定義されていなくても自動的に読み込めるようになります。 オートローダーを登録すれば、PHPがエラーで止まる前にクラスをロードする最後の チャンスが与えられます。

こんな手法がございます。

つまり、オートローディングの設定がないと
Hogeを使おうとする
・見つからない → エラー

な所が、オートローディングを使うと
Hogeを使おうとする
・見つからない → オートローディングをしてみる → それでも見つからないならエラー

となって、1クッション増えるわけなんですね。
クッションが増えるだけなので、「本当にどこにもそのクラスが存在しない」なら最終的にエラーにはなるのですが。

となると次は「オートローディングを、自力じゃなくて他人様が実装しておいてくれないもんかしらん?」となるのですが。
昨今のPHPにおいて、比較的高確率で「composer」を使っているのではないか、と思います。
で、もし「composerを使っている」とすると、composerにはオートローディングの実装があるので、こいつを借用する事ができます。

「public/index.php の最初の三行(vendor/autoload.php の挙動) https://note.com/gallu/n/ne1bcb25c412a 」を見ていただくと色々書いてあるのですが。
雑に書くと
・とりあえず、composerでインストールしたライブラリは、オートローディングが通用するようになる
感じです。

……だけだと「じゃぁ自作のクラスはどうすんだよ?」って話になるのですが。
実はcomposerのautoload.phpは、composer.jsonの['autoload']['psr-4']に「名前空間名 => ディレクトリ名」を書いておくと*2、こちらも自動で解決してくれるようになります。

なので、例えば上述であれば。
仮に名前空間を「\Gallu 」と仮定して

Libs/Hoge.php

<?php
namespace Gallu;

class Hoge
{
}

composer.json

{
(前略)
    "autoload": {
        "psr-4": {
            "Gallu\\" : "Libs/"
        }
    },
(後略)


t.php

<?php
require_once('./vendor/autoload.php');

//
$obj = new \Gallu\Hoge();

という風に書くと
・\Gallu\Hoge は、ない
・autoload.phpで登録されたオートローディングが動き出す
 → Galluって名前空間だからLibsを探してみる
  → HogeクラスだからHoge.phpってファイルを探してみる
  → あるから require する
・使えるようになった!!

という流れで、使えるようになります。
これがオートローディングの世界です。

まぁ「特殊なニーズがある」のであれば「自力実装を追加する」とかってのもあり得るんでしょうが、普通の用途ならまずは「あるモノを使ってみる」からでよいのではないかなぁ、と思います。
学習用途であれば一度は「自力実装」してみていただきたい所ですが。


じゃぁ改めて「useってなによ?」って話なのですが。
序盤にも書いた通り「別名の作成」となります。

かみ砕いて。

$obj = new \Gallu\Hoge();

ここ。
このまんま書くのであれば、useいらんです。
なお、\Gallu\Hogeを「完全修飾名」って言います。

これくらいならまぁまだ、とも思わんでもないのですが。
これが例えば

\Foo\Bar\Baz\Piyo\Fuga\Hoge

とかいうクラス名で、これが何カ所にも出てくる時に毎回毎回

$obj = new \Foo\Bar\Baz\Piyo\Fuga\Hoge() ;

とか書いていくのも、ちょっと「ぞっとしない」お話でございます。

こんな時に便利なのが別名。カタカナ表記だとエイリアス。これがuse。
こんな風に定義ができます。

use \Foo\Bar\Baz\Piyo\Fuga\Hoge as Hogera;

$obj = new Hogera() ;

こんな風に書くと「Hogera、って出てきたら \Foo\Bar\Baz\Piyo\Fuga\Hoge ってことで一つよろノ」って感じになります。
まぁエイリアスの名前通り、まんまの機能ですね。

さて。
わざわざ「クラス名と違う名前を毎回考えるのも面倒」だと思うので、割とこうなります。

use \Foo\Bar\Baz\Piyo\Fuga\Hoge as Hoge;

$obj = new Hoge() ;

これで目出度しとしてもよいのですが、「Hoge as Hoge」がちょいとウザい……で、実はこれが省略可能。

use \Foo\Bar\Baz\Piyo\Fuga\Hoge;

$obj = new Hoge() ;

スッキルする、と共に、多分見慣れた構文。
こうやってuseは「別名」を付ける機能を持ちます、が、一方で「クラス存在の解決」にはなんにも関わっておりませんので、従って「useだけ、だと、クラスは解決できない(かもしれない)」となります。


なので。
もし「ちゃんとuseしているのにクラスが解決できなくてPHPがエラーを吐く」のであれば、基本的には「所定の場所にクラスがかかれたファイルがない」か「そもそもそのクラスが存在していない」ので。
楽ちん順のやり方としては
・とりあえず find コマンドでファイルを探してみる(普通、 クラス名.php ってファイル名のはずなので)
・ちぃと時間がかかるかもですが、grepで「class クラス名」で全ファイルをあさってみる
で、対象のファイルが
・あるんだけど位置が違う
・ない
のあたりをつけておくと、次のアクションに繋がりやすいかなぁ、と思うです。

残りのお話は個別の内容になってしまうので、commonな内容としてはこの辺まで。
質問とかあったら、コメントにお願いいたします ノ

*1:まぁ普通、プログラムならrequire_onceですな

*2:正確には、書いた後 php composer.phar dump-autoload を実行すると

探索系アルゴリズム用のメモ

A* とか ダイクストラ法 とかが可視化されてるサイト。
こないだググったら案外うまくヒットしなかったので、メモ用に記録。

qiao.github.io

ロジック裏で把握するのも大事だけど、こんな風に可視化状態で見れるのもよいよね。