Pure MTML で NabeAzz

mixiのMTテンプレート大喜利大会(違う)に出したネタです。

NabeAzzというのは、えー、FizzBuzzの変形でー。
多分発祥はここだと思います。

そろそろ FizzBuzz に飽きた - にぽたん研究所

mixiでは文字数制限が怖かったのでコメント一切無しにしました。
ちょっとわかりづらいところもあると思うので、コメント付で再掲します。

自分の作ったテンプレートの目標は、ノープラグイン、ノースクリプト。MTタグのみでのNabeAzzです。

<mt:ignore><!-- 単純な線形合同法で乱数を生成。 --></mt:ignore>
<mt:setvarblock name="seed"><mt:date format="%S"></mt:setvarblock>
<mt:setvartemplate name="rnd">
    <mt:getvar setvar="seed" name="seed" op="*" value="13" />
    <mt:getvar setvar="seed" name="seed" op="+" value="5" />
    <mt:getvar setvar="seed" name="seed" op="%" value="24" />
    <mt:getvar name="seed">
</mt:setvartemplate>
 
<mt:ignore><!-- 配列の初期化はsetvar一発で。 --></mt:ignore>
<mt:setvar name="aho" value="www":"ktkr":"ぶーん":"だわよのさ":"へっぺふむーん">
<mt:setvar name="dog" value="ダワン":"キャイーン":"キャンキャン":"バウウウ":"ガルルル">
 
<mt:ignore><!-- 指定された配列からランダムに要素をとってくる。 --></mt:ignore>
<mt:setvartemplate name="get_suffix">
    <mt:setvarblock name="idx"><mt:getvar name="rnd" trim="1"></mt:setvarblock>
    <mt:setvarblock name="max"><mt:getvar name="$target" function="count"></mt:setvarblock>
    <mt:getvar setvar="idx" name="idx" op="%" value="$max">
    <mt:getvar name="$target" index="$idx">
</mt:setvartemplate>
 
<mt:ignore><!-- 以下、出力用ループ --></mt:ignore>
<ul>
<mt:for from="1" to="40">
    <li>
    <mt:getvar name="__counter__">
    <mt:unless name="__counter__" op="%" value="5">
        <mt:setvar name="target" value="dog">
        <mt:getvar name="get_suffix">
    </mt:unless>
    <mt:unless name="__counter__" op="%" value="3">
        <mt:setvar name="target" value="aho">
        <mt:getvar name="get_suffix">
    <mt:elseif name="__counter__" like="3">
        <mt:setvar name="target" value="aho">
        <mt:getvar name="get_suffix">
    </mt:unless>
    </li>
</mt:for>
</ul>

以下、解説。

<mt:ignore><!-- 単純な線形合同法で乱数を生成。 --></mt:ignore>
<mt:setvarblock name="seed"><mt:date format="%S"></mt:setvarblock>
<mt:setvartemplate name="rnd">
    <mt:getvar setvar="seed" name="seed" op="*" value="13" />
    <mt:getvar setvar="seed" name="seed" op="+" value="5" />
    <mt:getvar setvar="seed" name="seed" op="%" value="24" />
    <mt:getvar name="seed">
</mt:setvartemplate>

最初のこのブロックは、mt:setVarTemplateを使ってrndというルーチンを作成しています。
内容は、線形合同法という非常に素朴な乱数生成ルーチンです。

線形合同法 - Wikipedia

MTMLのみで乱数作れないかと思い立って調べてみたところ、なんだか簡単そうなので試してみました。今回のナベアツ問題なら精度の高い乱数は必要ないので、こんなのでも良いのではないかと思います。上限が24で最大周期も24、という貧弱貧弱なルーチンです。数字を調整すればもう少し良いものになるかもしれません。
乱数のシード(初期値)の値はmt:date(再構築が実行された時間を取得するタグ)からとっています。

<mt:ignore><!-- 配列の初期化はsetvar一発で。 --></mt:ignore>
<mt:setvar name="aho" value="www":"ktkr":"ぶーん":"だわよのさ":"へっぺふむーん">
<mt:setvar name="dog" value="ダワン":"キャイーン":"キャンキャン":"バウウウ":"ガルルル">

あらかじめ配列に、アホ発言と犬発言を準備しておきます。ちょっと変な記法の解説はこちら

<mt:ignore><!-- 指定された配列からランダムに要素をとってくる。 --></mt:ignore>
<mt:setvartemplate name="get_suffix">
    <mt:setvarblock name="idx"><mt:getvar name="rnd" trim="1"></mt:setvarblock>
    <mt:setvarblock name="max"><mt:getvar name="$target" function="count"></mt:setvarblock>
    <mt:getvar setvar="idx" name="idx" op="%" value="$max">
    <mt:getvar name="$target" index="$idx">
</mt:setvartemplate>

ここで、またmt:setVarTemplateを使ってルーチンを作成しています。
このルーチンでは、「target」という変数に指定された名前の変数から、要素を一つ取り出して出力する、という作業を行っています。
ポイントはここでしょうか。「mt:getvar name="$target" index="$idx"」。
MTのテンプレートエンジンでは、タグそのものの処理が行われる前の段階で、モディファイア内の「$」記法の変数の展開が行われます。そのため、一部のタグに対しては、変数の名前を別の変数に入れて渡す、ということが可能となっています。
この場合では、「target」変数に「aho」または「dog」という文字列を入れておくと、まずテンプレートのパーサーが「$target」を展開してahoなどの文字列に置き換え、それを更にmt:getVarタグが解釈して、aho変数の中身を参照しに行ってくれる、というカラクリです。
これが出来るのは、モディファイアとして変数名を受け取ることが出来る一部のタグのみですが、応用するとかなりパワフルなことが出来るのではないかと思います。

<mt:ignore><!-- 以下、出力用ループ --></mt:ignore>
<ul>
<mt:for from="1" to="40">
    <li>
    <mt:getvar name="__counter__">
    <mt:unless name="__counter__" op="%" value="5">
        <mt:setvar name="target" value="dog">
        <mt:getvar name="get_suffix">
    </mt:unless>
    <mt:unless name="__counter__" op="%" value="3">
        <mt:setvar name="target" value="aho">
        <mt:getvar name="get_suffix">
    <mt:elseif name="__counter__" like="3">
        <mt:setvar name="target" value="aho">
        <mt:getvar name="get_suffix">
    </mt:unless>
    </li>
</mt:for>
</ul>

最後に出力用にループを回しています。条件分岐はそんなに変なことはしてません。testモディファイアを使うともっと短くなるんだろうけど、使い方がわからないので諦めました。

TechMemo

  • _math_operationに直接式をかけると良いと思った。op="($aho + $dog) * $rnd"みたいな。駄目なら、配列で渡せるというのはどうだろう。op="+","*" value="$aho","5"のように。
  • 配列orハッシュ変数の展開は今のところgetvarの機能となっているが、これはbuild側でやるべきじゃないだろうか。
  • mt:date format="%s"って何で無いんだ?小文字で、0埋め無しの秒を取れると思ってたら取れなかった。ここで一番嵌った。需要が無いから?
  • <mt:getvar setvar="seed" name="seed" op="*" value="13">ここのモディファイアの並べ方はちょっと悩んだ(動作的には関係ないが、読みやすさの意味で)が、最終的にはc、perl系の言語っぽく並べるのが一番わかり易いと思った。「$seed = $seed * 13」 と書くのと同じ並び。mt:var系列は複雑で把握しきれない。もっと短く書けた気がする。。。