Webプログラマの夏休み PHPでNゲージ模型を自由自在に動かそう[動画つき]

第3回シミュレータ作りました

前回まで、ハードウェアがないとプログラムを動作させられなかったので、読者の皆さんにも疑似体験していただけるようパソコンの上で動作が再現できるシミュレータを開発しました。まずこれをご紹介しましょう。

ちなみに、第2回補足ではハードウェアトラブルが起きてしまいましたが、ソフトウェアの方はこのシミュレータを使ってテストしています。

シミュレータとその使い方

シミュレータの動作環境として、PHPの4または5を利用しています。Webサーバから利用できる必要があり、gdライブラリも使いますが、一般的なLinuxのディストリビューションであればどれもパッケージが用意されていると思います。サンプルの制御プログラムを動かす場合にはコマンドライン版のPHPも必要ですが、これもLinux環境なら問題ないと思います。Windowsでも問題なく利用できます。

まず、シミュレータや関連ファイルを以下のリンクからダウンロードしてください。

なお、前回まではコントローラを指すようURLを192.168.0.100にしていましたが、今回からはシミュレータを指すようにしています。

ダウンロードしたファイルを解凍したら、適当なWeb公開用のディレクトリに、simrail.php(シミュレータ本体)とrailconf.txt(設定ファイル)を置きます。railconf.txtはWebサーバから書き換えますので、chmod a+rw railconf.txtなどとして、書き込み権限を与えておいてください。

なお、サンプルはすべて/gh/rail/simrail.phpにアクセスするようになっていますので、ここにファイルを配置すると簡単です。ただしインターネットには公開しない方がよいでしょう(usleepを使っているのでサーバの負荷が高くなります⁠⁠。

リスト1 初期状態のrailconf.txt
#    10 13    20       30    37 40       50
# 10     _____+--------+_____
# 13    +                    +
#     |                        |
# 20 +                          +
#     |                        |
# 27    +_____          _____+
# 30          +---00--+

R00,3030,2030,1327,1020,1313,2010,3010,3713,4020,3727,3030

TA,2030,3030,50
TB,2010,3010,50
S0000,D

この状態で、上記のsimrail.phpにWebブラウザからアクセスすると、シミュレータの画面が表示されます。初期状態では、環状のレールを車両が2つ走行している状態になります。

初期状態
初期状態

先にも書きましたが、プログラムをテストするときは、まずレール配置などを記述したrailconf.txtという名前のファイルを用意します。railconf.txtは実行に従って上書きされてしまいますので、別に用意したテキストファイルからコピーするのがよいでしょう。たとえば前回の最後に紹介した2重ループのレイアウトサンプルであれば、ダウンロードして解凍した中のrail22.txtを使用します。

cp rail22.txt railconf.txtなどとして、railconf.txtに反映してください。これで、シミュレータの画面が変わるはずです。

rail22.txtをrailconf.txtにコピーしたときのシミュレータ画面
rail22.txtをrailconf.txtにコピーしたときのシミュレータ画面
リスト2 rail22.txt(2重ループのレイアウト)
#    10 13    20  25   30    37 40  45   50    57 60
# 10     _____+---|-04-+--------+---|-00-+
# 13    +            p2 ~~~~~+            ~~~~~+
#     |                        |                  |
# 20 +                          +                  +
#     |                        |                  |
# 27    +_____       p3 _____+            _____+
# 30          +05-|----+--------+01-|----+

R00,4530,5030,5727,6020,5713,5010,4510
R01,4530,4030
p0301,4030,2530
P0304,2530,3030,3727,4020
R04,4510,4010
p0204,4010,2510
P0204,4020,3713,3010,2510
R05,2530,2030,1327,1020,1313,2010,2510

TA,4030,4530,50
TB,4510,5010,50
S0000,AAppAA

最後に、線路のレイアウトに応じたプログラム(スクリプト)を実行します。このとき、リスト中のURLを自分のサーバのsimrail.phpを指すように変更してください。

コマンドラインから./rail22.phpなどと入力してスクリプトを実行すると、シミュレータの画面に様子が表示されます。起動できない場合は、php -f rail22.phpも試してみてください。

rail22.phpを実行したところ(シミュレータが古いバージョンのため、多少動作がぎこちないところがあります)
ニコニコ動画:https://www.nicovideo.jp/watch/sm7926332

サンプルには、そのほかにも第1回、第2回で扱ったレイアウトに対応したプログラムが入っています。それぞれのスクリプトと設定ファイル(railconf.txtにコピーするもの)は、以下のようになりますので試してみてください。

表1 プログラム(レールレイアウト)とファイル名
直線レイアウトrail11.txt
第1回サンプルrail10.php、rail11.php、rail11a.php
第2回サンプルrail21.php
引き込み線レイアウトrail12.txt
第1回サンプルrail12.php
2重ループレイアウトrail22.txt
第2回サンプルrail22.php

シミュレータの仕組み

本題とはあまり関係ないのですが、シミュレータの仕組みについて説明しておきましょう。普通、連続的なシミュレーションには常駐プロセスを使用し、そこと他のプログラムが通信するスタイルを取るのが普通だと思います。ただ、ここではあまり複雑なことをしたくなかったので、以下のような方法にしました。

ブラウザや制御プログラムからsimrail.phpがアクセスされると、railconf.txtを読み込んだあと、前回からの経過時間を調べます。そして、その時間分のシミュレーションをおこなったあと、リクエストや画面表示の処理をおこないます。ゲームで、プレイヤーがいない間に起こったできごとを、前回セーブしたときからの経過時間に応じて、ロード時に再現するものがありますが、それと似たような考え方です。

この方法を使うには、あるリクエストのときに行ったシミュレーションの途中の状態を、次のリクエストに引き継げる必要があります。そこで、railconf.txtにシミュレーションの結果を毎回書き出しています。複数のリクエストが同時に来る可能性があるため、flockで排他処理を行っています。またrefreshを使い、定期的にブラウザに再読み込みさせることで画面を更新し、定期的にリクエストが発生することでシミュレーションが進むようになっています。

車両のオブジェクト化

シミュレータができましたので、さっそく使ってみましょう。今回のテーマは、車両のオブジェクト化です。メインループから、それぞれの車両オブジェクトのstepメソッドを順番に呼び出す形にします。stepメソッドの中では、自分の前方のレールが空いているかどうかを調べ、空いていたらそこまで車両を進めるようにします。stepメソッドの中で待つと、他の車両も動かなくなってしまうので、注意が必要です。

車両の有無は、起動時に全部のレールをセンスすることで自動的に見つけるようにしてみました。これで、車両を適当に配置してからプログラムを実行すれば、自動的に処理に反映されることになります。なお実際には、レールと車輪の接触不良などがあると検出に失敗することがあります(今までのサンプルで、現在のレールから去ったのを検出するのではなく、次のレールに現れたことを検出するようにしていたのは、このためです⁠⁠。

2つの車両が1つのレールに入らないように、レールのオブジェクトの中に「使用中」のフラグを用意しました。これから進もうとする先のレールが使用中であれば、いったんstepメソッドからリターンし、次回stepメソッドが呼ばれたときにもう一度挑戦します。使用中でなければ、自分が使用中にして、そこまで車両を進めます。進んだら、いま来たレールの使用中を解除します。今回は、使用中フラグの管理をlockメソッドで行うようにしました。

図1 レールセンスできるように接続したループレイアウト
図1 レールセンスできるように接続したループレイアウト
レールセンスできるように接続したループレイアウト
リスト3 rail31.txt(シミュレータ用設定ファイル)
R00,2010,2713
R01,2713,3020
R02,3020,2727
R03,2727,2030
R04,2030,1327
R05,1327,1020
R06,1020,1313
R07,1313,2010

TA,2010,1313,50
TB,2030,2727,50
S0000,AAAAAAAA
リスト4 rail31.php(オブジェクト化サンプルスクリプト)
#! /usr/local/bin/php
<?php

$url = "http://localhost/gh/rail/simrail.php5";

class	rail {
    var    $number;
    var    $lockcount = 0;
    function    rail($number = "00") {
        $this->number = $number;
    }
    function    drive($speed = 0) {
        global    $url;
        
        if ($speed >= 0)
            $command = substr("ABCDEF", min($speed, 5), 1);
        else
            $command = substr("abcdef", min(-$speed, 5), 1);
        file_get_contents($url."?".$this->number."=".$command);
    }
    function    sense() {
        global    $url;
        
        return file_get_contents($url."?".$this->number."=S") + 0;
    }
    function    lock($mode = 1) {
        printf("rail(%s)->lock(%d) lockcount(%d)\n", $this->number, $mode, $this->lockcount);
        switch ($mode) {
            default:
                die("lock({$mode})");
            case    0:
                if (($this->lockcount--) <= 0)
                    die("unlock failed.");
                return;
            case    1:
                if ($this->lockcount > 0)
                    return -1;    /* failed. */
                $this->lockcount++;
                return 0;
            case    -1:
                $this->lockcount = 0;
                return;
        }
    }
}

class    revrail extends rail {
    function    drive($speed = 0) {
        parent::drive(-$speed);
    }
}

#
# +---r7---|---r0---|---r1---+
# |                          |
# |                          |r2
# |r6                        |
# |                          |
# +---r5---|---r4---|---r3---+
#

$raillist = array();
$raillist[] =& new rail("00");
$raillist[] =& new rail("01");
$raillist[] =& new rail("02");
$raillist[] =& new rail("03");
$raillist[] =& new rail("04");
$raillist[] =& new rail("05");
$raillist[] =& new rail("06");
$raillist[] =& new rail("07");


class    train {
    var    $pos;
    var    $starttime = 0;    /* >0: wait in station */
    var    $mode = 0;    /* 0:in station 1:run */
    function    train($currentpos = 0) {
        global    $raillist;
        
        $this->pos = $currentpos;
        if (($raillist[$this->pos]->lock(1)) < 0)
            die("lock({$this->pos}) failed.");
    }
    function    step() {
        global    $raillist;
        
        if ($this->starttime > 0) {
            if (time() < $this->starttime - 60)
                ;    /* system-clock changed */
            else if (time() > $this->starttime)
                ;
            else
                return;
            $this->starttime = 0;
        }
        
        $currentrail =& $raillist[$this->pos];
        $nextrail =& $raillist[($this->pos + 1) % count($raillist)];
        switch ($this->mode) {
            default:
                die("mode: ".$this->phase);
            case    0:
                if ($nextrail->lock(1) < 0)
                    return;        /* lock failed. */
                $currentrail->drive(3);
#                $currentrail->drive(rand(1, 5));
                $this->mode++;
                return;
            case    1:
                if ($nextrail->sense() == 0)
                    return;        /* not arrived yet */
                $this->starttime = time() + 5;
                $this->mode++;
                return;
            case    2:
                $currentrail->drive(0);
                $currentrail->lock(0);    /* unlock */
                $this->pos = ($this->pos + 1) % count($raillist);
                $this->mode = 0;
                $this->starttime = time() + 5;
                return;
        }
    }
}


$trainlist = array();
foreach ($raillist as $key => $dummy) {
    $raillist[$key]->drive(0);
    if (($raillist[$key]->sense()))
        $trainlist[] =& new train($key);
}


for (;;) {
    foreach ($trainlist as $key => $dummy)
        $trainlist[$key]->step();
}

?>
車両をオブジェクト化して制御
ニコニコ動画:https://www.nicovideo.jp/watch/sm7926405

次回は、今回作成したシミュレータ用のレイアウトエディタを作ってみます。

おすすめ記事

記事・ニュース一覧