Twitter Bot おうじょったーを作るまで 2

ドラクエ1に出てくる「王女の愛」というアイテムの Bot おうじょったー を作った時のメモ。

リプライしてくれたユーザの位置を決める

おうじょたーは自分にリプライしてくれたユーザの twitter の利用具合から、そのユーザの現在位置を決めています。前回のエントリで地図上の入れる場所と入れない場所は把握できたので、その後の処理、ユーザの利用具合を調べる〜現在位置を決める工程の説明をしてみます。

ユーザの利用具合を調べる

リプライユーザは API経由で RSS をゲットして調べます。その後各ユーザの twitter 利用具合(フォローしている数、フォローされている数、投稿数)を調べます。具体的には直接その人の twitter ページを参照して必要な情報を取得します。本当は、真面目に API から取得したほうが将来的に安全です。

# twitterId からフォローしている/されている数、投稿数を取得
sub getTwitParamById {
    my $id   = shift;
    my $data = get("http://twitter.com/$id");
    my $html = HTML::TagParser->new($data);
    
    return map {
        my $elem = $html->getElementById($_);
        my $val  = $elem ? $elem->innerText() : 0;
        $val =~ s/,//g;
        $val;
    } ('following_count', 'follower_count', 'update_count');
}


見ての通り、 http://twitter.com/HOGEHOGE のページを取得して HTML::TagParser でそれぞれの値をゲットしています。HTMLのID値が設定されているので簡単に出来ていますが、将来 HTML が変わってしまうと、変更にあわせて修正が必要になってしまいます。

経験値を求める

取得したフォローしている数、されている数、投稿数から経験値を決めます。値の範囲はファミコン版のドラクエ1と同じで 0 から 65535 まで。具体的な計算方法は載せませんが、フォローされている数と投稿数が多いほど経験値が高くなるようにしています。

経験値からレベルを求める

経験値が決まるとレベルも決まります。次の関数で現在のレベルと、次のレベルに達するためにひつような経験値を求めています。
書き方が汚いのは後から思いつくままに機能を追加したからです。

sub exp2lv {
    my $exp = shift;
    my @a = (    0,    7,   23,   47,  110,  220,  450,  800, 1300, 2000,
              2900, 4000, 5500, 7500,10000,13000,17000,21000,25000,29000,
             33000,37000,41000,45000,49000,53000,57000,61000,65000,65535 );
    
    my $lv = grep { $exp >= $_ } @a;
    
    return ($lv, $a[$lv] ? $a[$lv]-$exp : 0);
}

レベルから立ち入り可能エリアを決める

低レベルクリアとかで無い限り、レベルが上がるにつれて行動範囲が広がります。そこでおうじょったーでもレベルに応じて移動範囲を決めています。マップを 8 x 8 の 64区画に分割して、レベル 5 単位で立ち入り可能エリアを決めています。レベルが上がるにつれて、新しく立ち入り可能となった場所によく出没するような補正をかけています。これも後から思いついた機能なんで汚いコードです。すいません。

sub lv2area {
    my $lv = shift;
    my @ret;
    
    my @a = (
        # Lv  1- 5
        [  2, 3,10,11,18,19,26 ],
        # Lv  6-10
        [  0, 1, 8, 9,12,13,16,17,20,21 ],
        # Lv 11-15
        [  4, 5, 6, 7,24,25 ],
        # Lv 16-20
        [ 32,33,34,36,40,41,42,43,44,45,48,49,50,51,52,53,56,57,58,59,60 ],
        # Lv 21-25
        [ 28,29,30,31,37,38,39,46,47,54,55,61,62,63 ],
        # Lv 25-30
        [ 27,35,61 ],
        );
    
    my $cnt = 0;
    for my $idx (0..int(($lv - 1) / (@a - 1))) {
        for (0..$cnt) {
            map { push @ret, $_ } @{$a[$idx]};
        }
        $cnt++;
    }
    
    return @ret;
}

位置を決める

立ち入り可能場所がきまったら経験値なんかから割り算やあまりの計算をして適当に(ランダムではなく)どのエリアのどのポイントかを決めます。136 x 136 のマップを 64 分割してますので、一つのエリアで 289 ヶ所のポイントがあります。
そうして決まった位置が海や山でなくちゃんと入れるかを調べるために、前回のエントリで作成したマップ情報を使って調べます。

もし入れない場所だった場合、同一エリア内で位置を補正して入れる位置を探します。

@map = ( 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 
         0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 
         ..... ); # 前回作ったマップのデータ

# 居場所を探す
sub get_pos {
    my ($mapidx, $spot) = @_;
    my ($x, $y);

    do {
        $x =     $mapidx % 8  * 17 +     $spot % 17 ;
        $y = int($mapidx / 8) * 17 + int($spot / 17);
        
        $spot -= 13;
        $spot  = 289 - $spot if $spot < 0;
    } until (is_enabled($x, $y));
    
    return ($x, $y);
}


# その場所は入れる?
my @map;
sub is_enabled {
    my ($x, $y) = @_;
    my $idx = $y * 136 + $x;
    return $map[int($idx/32)] & 0x01 << ($idx % 32);
}

おわりに

そうやって決まった場所を「わたしのいる おしろは (きた|みなみ) に 99 (ひがし|にし) に 99 のほうこうです。@hogehoge さまをローラはおしたいもうしております。」と twitter にポストします。


画像から 0 / 1 データを作って利用するということを説明したかったのだけど、色々と適当な感じになってしまいました。多分もっといいやり方があるはずなんで、それはこれからの勉強課題としておきます。