新規作成
OpenIDで認証してエントリーを新規作成します
共有
エントリーにはOpenIDで閲覧と編集に制限かけることができます
変更履歴
編集履歴もあるので、コラボレーションにも活用できます

モブストライクについて全裸で考えてみた

課金による無限レベルアップ効率についてちょっと計算

ヤマモトさんがついったーでつぶやいてらっしゃったことを僕なりにまとめてみました。

 

 

レベルアップごとに上昇する必要経験値

1レベルごとに上昇する必要経験値 = 12.5exp

母国に旅行の経験値効率 = 95exp / 46体力 = 2.06


無限レベルアップ状態を維持するために上げなければならない体力 = 12.5exp / 2.06 = 6.07sp

レベルアップボーナスのみでの不足分 = 6.07 - 5 = 1.07

 

 

リワードポイントをスキルポイントと引き換えて無限レベルアップ

不足分1.07spを、課金でのスキルポイントブーストによって補う


RPのスキルポイント効率 = 14rp / 4sp = 3.5

不足分1.07spを補うのに必要なRP = 1.07sp * 3.5 = 3.75rp

レベルアップすると1rpボーナスがあるので、課金によって必要なRP = 3.75rp - 1rp = 2.75rp

 

 

リワードポイントを体力回復に使用して無限レベルアップ

同様に必要経験値量/2の体力で、RPによる体力回復を利用して無限LvUpを行うと

レベルアップごとに 10rp - 1rp = 9rp 必要になる。

また、連続レベルアップを止めた時点で保有する体力に大きな差もでる。

 

 

レベルアップの効率に焦点を当てると、ステータスアップのほうが効率が良い

RP購入による無限レベルアップの、日本円効率は、ステータスアップのほうが3倍以上良い。


15,000円課金すると 700 / 2.75 = 254.5レベルのスパイラルが可能になる

これにより上昇する体力は約1500となる

 

 

ヤマモトさんのご発言

http://twitter.com/ymtkyk/status/6643610318

http://twitter.com/ymtkyk/status/6643627785

http://twitter.com/ymtkyk/status/6643648792

http://twitter.com/ymtkyk/status/6643682363

created by http://www.hatena.ne.jp/kabitter/

LWP::UserAgent::parse_headerのベンチマーク

titleタグとか勝手にparseするやつね


結果

% perl parse_header.pl
            Rate        lwp lwp-header
lwp        543/s         --       -19%
lwp-header 673/s        24%         --

script

use Benchmark;
use LWP::UserAgent;

my $count = 3000;

Benchmark::cmpthese( $count , {
    'lwp' => sub {
        my $ua = LWP::UserAgent->new;
        my $res = $ua->get('http://localhost/')
    },
    'lwp-header' => sub {
        my $ua = LWP::UserAgent->new;
        $ua->parse_head(0);
        my $res = $ua->get('http://localhost/');
    },
});

created by blog.nomadscafe.jp

LWP vs AnyEvent::HTTP (+ Coro)

AnyEvent::HTTPで同時にアクセスしないようにしてテスト

% perl -I. anyevnet_http.pl
Benchmark: timing 3000 iterations of anyevent, lwp...
  anyevent:  3 wallclock secs ( 1.57 usr +  1.27 sys =  2.84 CPU) @ 1056.34/s (n=3000)
       lwp:  6 wallclock secs ( 4.25 usr +  1.22 sys =  5.47 CPU) @ 548.45/s (n=3000)

script

use Benchmark;
use LWP::UserAgent;
use AnyEvent;
use AnyEvent::HTTP qw//;

$AnyEvent::HTTP::MAX_PER_HOST = 1;
my $count = 3000;

timethese( $count , {
    'lwp' => sub {
        my $ua = LWP::UserAgent->new;
        my $res = $ua->get('http://localhost/')
    },
    'anyevent' => sub {
        my $cv = AE::cv;
        AnyEvent::HTTP::http_get 'http://localhost/', sub { $cv->send('1'); };
        $cv->recv;
    },
});

LWPは速くない


同じようにCoro::LWP

若干遅くなる

Benchmark: timing 3000 iterations of coro...
      coro:  5 wallclock secs ( 1.75 usr +  3.83 sys =  5.58 CPU) @ 537.63/s (n=3000)

use Benchmark;
use Coro;
use Coro::LWP;
use LWP::UserAgent;

my $count = 3000;

async {
timethese( $count , {
    'coro' => sub {
        my $ua = LWP::UserAgent->new;
        my $res = $ua->get('http://localhost/')
    },
});
exit;
};

schedule;
created by blog.nomadscafe.jp

Coro::Channelとtimeoutを組み合わせる

use Plack::Request;
use HTTP::Request;
use Time::HiRes;
use Coro;
use Coro::Channel;
use Coro::AnyEvent;
use Coro::LWP;
use LWP::UserAgent;

my $UA_WORKER_CORO_DESC_IN_WORK = "ua_worker_coro_desc_in_work";
my $UA_WORKER_CORO_DESC_NOT_WORK = "ua_worker_coro_desc_not_work";
my $UA_WORKER_CORO_TIMEOUT_MESSAGE = "ua_worker_coro_timeout";
my $MAX_UA_WORKER = 100;

sub coro_timeout {
    my $timeout = shift;
    my $cb = shift;
    my $coro = $Coro::current;
    $coro->{timeout_at} = Time::HiRes::time() + $timeout;
    my $installed_destroy = $coro->{on_destroy_once} ? 1 : 0;
    $coro->{on_destroy_once} = $cb;
    if ( !$installed_destroy ) {
        $coro->on_destroy( sub {
            my $message = shift;
            return if $message ne $UA_WORKER_CORO_TIMEOUT_MESSAGE;
            my $cb = delete $coro->{on_destroy_once};
            return unless $cb;
            $cb->($message);
        });
    }
}

sub ua_worker {
    my $channel = shift;
    async {
        $Coro::current->desc($UA_WORKER_CORO_DESC_NOT_WORK);
        while(1) {
            my $req = $channel->get();
            $Coro::current->desc($UA_WORKER_CORO_DESC_IN_WORK);
            my $ua = LWP::UserAgent->new( timeout => $req->[1] ); #timeout is ignored
            coro_timeout( $req->[1], sub {
                $req->[2]->send( LWP::UserAgent::_new_response( $req->[0], &HTTP::Status::RC_INTERNAL_SERVER_ERROR, "request timeout" ) );
            });
            my $res = $ua->request($req->[0]);
            $Coro::current->desc($UA_WORKER_CORO_DESC_NOT_WORK);
            $req->[2]->send($res);
        }
    }
}

# build worker threads
my $worker_timer;
sub build_ua_channel {
    warn "[$$] build channel";
    my $channel = Coro::Channel->new();
    for ( my $i=0; $i < $MAX_UA_WORKER; $i++ ) {
        ua_worker($channel);
    }
    $worker_timer = AnyEvent->timer(
        after => 0.5,
        interval => 0.5,
        cb => sub {
            #timeout
            my $now = Time::HiRes::time;
            my @lwp_coro = grep { $_->desc eq $UA_WORKER_CORO_DESC_IN_WORK } Coro::State::list;
            for my $coro (@lwp_coro) {
                if ($now > $coro->{timeout_at}) {
                    $coro->cancel($UA_WORKER_CORO_TIMEOUT_MESSAGE);
                }
            }

            #spawn worker
            my $dead_worker = $MAX_UA_WORKER - 
                scalar( grep { $_->desc eq $UA_WORKER_CORO_DESC_IN_WORK || $_->desc eq $UA_WORKER_CORO_DESC_NOT_WORK } Coro::State::list );
            for ( my $i=0; $i < $dead_worker; $i++ ) {
                warn "[$$] respwan";
                ua_worker($channel);
            }
        }
    );
    return $channel;
}

my $ua_channel;
sub async_ua_request {
    my $request = shift;
    my %args = @_;
    my $timeout = $args{timeout} || 180;
    my $cb = $args{cb} || sub {};
    $ua_channel ||= build_ua_channel();

    my $cv = AE::cv;
    $ua_channel->put( [$request, $timeout, $cv ] );
    $cv->cb( sub { $cb->(shift->recv) } );
}

my $hanlder = sub {
    my $env = shift;
    my $req = Plack::Request->new($env);

    my $cv = AE::cv;

    my $request = HTTP::Request->new(
        $env->{REQUEST_METHOD},
        URI->new_abs($env->{REQUEST_URI},'http://localhost/'),
        [
            map {
                (my $field = $_) =~ s/^HTTPS?_//;
                ( $field => $env->{$_} );
            }
            grep { /^(?:HTTP|CONTENT|COOKIE)/i } keys %$env
        ],
        $req->raw_body
    );

    async_ua_request( $request, timeout => 1, cb => sub {
        my $res = shift;
        my @res_header;
        $res->headers->scan(sub{
            push @res_header, @_;
        });
        $cv->send([
            $res->code,
            \@res_header,
            [$res->content]
        ]);
    });
    
    return sub {
        my $start_response = shift;
        $cv->cb(
            sub {
                $start_response->( shift->recv );
            }
        );
    };
}

TODO: global変数の部分をpackageにしてオブジェクトの中に納める

created by blog.nomadscafe.jp

Coro::Channelをつかってみた

use Plack::Request;
use HTTP::Request;
use Data::Dumper;
use Coro;
use Coro::Channel;
use Coro::AnyEvent;
use Coro::LWP;
use LWP::UserAgent;


# worker threads
sub build_channel {
    warn "[$$] build channel";
    my $channel = Coro::Channel->new();
    for my $i (0..100) {
        async {
            while(1) {
                my $req = $channel->get();
                my $ua = LWP::UserAgent->new();
                my $res = $ua->request($req->[0]);
                my @res_header;
                $res->headers->scan(sub{
                    push @res_header, @_;
                });
                $req->[1]->send([
                    $res->code,                    \@res_header,
                    [$res->content]
                ]);
            }
        }
    }
    return $channel;
}

my $channel;
my $hanlder = sub {
    my $env = shift;
    my $req = Plack::Request->new($env);

    $channel ||= build_channel();

    my $request = HTTP::Request->new(
        $env->{REQUEST_METHOD},
        URI->new_abs($env->{REQUEST_URI},'http://localhost/'),
        [
            map {
                (my $field = $_) =~ s/^HTTPS?_//;
                ( $field => $env->{$_} );
            }
            grep { /^(?:HTTP|CONTENT|COOKIE)/i } keys %$env
        ],
        $req->raw_body
    );

    my $cv = AE::cv;
    $channel->put( [$request,$cv ] );

    return sub {
        my $start_response = shift;
        $cv->cb(
            sub {
                $start_response->( shift->recv );
            }
        );
    };
}

created by blog.nomadscafe.jp

mobstrikeに於ける他攻略要素についての考察等

mobstrikeの攻略とか

自由に編集して下さって結構です。

各階層に於けるクリアボーナス

ごろつき(lvls 1 - 4)
体力 -50秒短縮
ちんぴら(lvls 5 - 8)
スタミナ -50秒短縮
ソルジャー(lvls 9 - 12)
体力 -40秒短縮
用心棒(lvls 13 - 17)
スタミナ -40秒短縮
ヒットマン(lvls 18 - 24)
体力 -30秒短縮
支部長(lvls 25 - 34)
スタミナ -30秒短縮
アンダーボス(lvls 60 - 99)
体力 -20秒短縮
ボス(lvls 99 ~)
スタミナ -20秒短縮

各々をクリア(全ての仕事をレベル3の習熟度100%)することでボーナスが享受出来る。勿論、ごろつき->ソルジャーの様な順にクリアしても問題無く効果を享受出来る様だ。

懸賞金TIPS

  • 気になったマフィアに掛ける事で、現状のそいつを倒せるマフィアを探す事が出来る。

上限

ファミリ
2510#(ただし、URLによる追加は可能な為、更なる高見は目指す事が可能。
created by juner.net

Coro::LWPでtimeout + timeoutきりかえ

Coro::LWPでタイムアウトさせるに加えて、1つのasyncの中で複数回LWPを行ない、それぞれのtimeout、on_destory時の挙動を変更するtest


何がやりたいかというと

A->B->C

というリクエスと順は保ちつつ、それぞれを非同期でアクセスするものです

proxyっぽいものを実装する際に、Aで認証、Bで実際のアクセス、Cでフィルタ動作

を狙ってます。

use Time::HiRes;
use Coro;
use Coro::LWP;
use LWP::UserAgent;
use Data::Dumper;

sub coro_timeout {
    my $timeout = shift;
    my $cb = shift;
    my $coro = $Coro::current;
    $coro->{timeout_at} = Time::HiRes::time() + $timeout;
    my $installed_destroy = $coro->{on_destroy_once} ? 1 : 0;
    $coro->{on_destroy_once} = $cb;
    if ( !$installed_destroy ) {
        $coro->on_destroy( sub {
            my $message = shift;
            my $cb = delete $coro->{on_destroy_once};
            return unless $cb;
            return if $message ne 'timeout';
            $cb->($message);
        });
    }
}

my $parent_loop = 0;
for my $timelimits ( [10,10,10],[1,10,10],[10,1,10],[10,10,1] ) {
    $parent_loop++;
    my $parentloop = $parent_loop;
    async {
        $Coro::current->desc("LWP");
        my $childloop = 0;
        for my $timelimit ( @$timelimits ) {
            $childloop++;
            my $ua = LWP::UserAgent->new;
            coro_timeout($timelimit, sub {
                 my $message = shift;
                 warn sprintf "parent: %s, child: %s cancel because %s", $parentloop, $childloop, $message;
            });
            my $res = $ua->get("http://localhost/tmp/sleep3.cgi");
            warn "parent: $parentloop, child: $childloop done!";
        }
        warn "parent: $parentloop done all!";
    };
}

my $w; $w = AnyEvent->timer (
    after => 0.5,
    interval => 1,
    cb => sub {
        my $now = Time::HiRes::time;
        my @lwp_coro = grep { $_->desc eq "LWP" } Coro::State::list;
        for my $coro (@lwp_coro) {
            if ($now > $coro->{timeout_at}) {
                $coro->cancel("timeout");
            }
        }
        if (@lwp_coro == 0) {
            exit;
        }
    }
);

schedule;

実行結果

% perl coro_lwp.pl
parent: 2, child: 1 cancel because timeout at coro_lwp.pl line 41.
parent: 4, child: 1 done! at coro_lwp.pl line 44.
parent: 3, child: 1 done! at coro_lwp.pl line 44.
parent: 1, child: 1 done! at coro_lwp.pl line 44.
parent: 3, child: 2 cancel because timeout at coro_lwp.pl line 41.
parent: 4, child: 2 done! at coro_lwp.pl line 44.
parent: 1, child: 2 done! at coro_lwp.pl line 44.
parent: 4, child: 3 cancel because timeout at coro_lwp.pl line 41.
parent: 1, child: 3 done! at coro_lwp.pl line 44.
parent: 1 done all! at coro_lwp.pl line 46.
created by blog.nomadscafe.jp

mobstrikeに於けるファミリーの追加規則

モブストライク( http://mfga.jp/ )に於ける、ファミリの追加規則等について。

MobStrike登録可能アカウントについて

※ログイン画面に表示されるAOLmailとAIMは同じAOL Accountによって機能する物であるので、同様の物と判断し、AOLMailに統一した。

MobStrike参加前

mixi
登録録する時のマイミクシィの中のMobStrike参加者分のファミリ数となる。
twitter
登録する時のフォローしている最新から100人目迄の人の中からMobStrike参加者が居るとその人数分のファミリ数となる。
各種メール
登録する時の相互アドレス登録しているMobStrike参加者分のファミリ数となる。

MobStrike参加後

mixi
登録録する時のマイミクシィの中の誰かがMobStrikeに参加するとファミリが増える。
twitter
登録する時のフォローしている最新から100人目迄の人の中からMobStrikeに参加するとファミリが増える。
各種メール
登録する時の相互アドレス登録の中の誰かがMobStrikeに参加するとファミリが増える。

新たにファミリを増やしたい場合

  • ファミリーにて取得出来るURLから登録して貰うと各種条件に関わらずファミリが1増える。
  • ボスにリワードポイント20ポイントを払ってファミリーを2×n人追加出来る。

ポイント

  • 初期ファミリ数や随時追加ファミリ数は登録時のフォロワやマイミクシィなどの相互登録の数による固定数である事。
  • twitterはAPIに取得制限があるため、登録時の最新フォロワの100人目までしか取れない為、フォローしまくってフォローして貰いやすくするという方法によって妨害か可能である事。
  • mixiや各種メールは取得の制限が無い為、幾らでも増やせる事。

参考資料

モブストライク初心者マニュアル

Twitter/Search #mbst

注意

尚、結構な情報が推測した物なので間違っていたらコメントや編集などをして頂けると幸いです。

又、加筆修正は随時受け付けております。

created by juner.net

AnyEvent::MPRPCでMessagePack RPC

MessagePack RPCは全てのメッセージにシーケンス番号をいれることで、1つのTCP接続でpipeliningのような遅延リターンができる。

Gearmanでやりたかったいめーじはこちらのほうがちかい。


server側

use AnyEvent;
use AnyEvent::MPRPC::Server;


my $server = AnyEvent::MPRPC::Server->new( port => 4423 );
$server->reg_cb(
    'sleep' => sub {
        my ( $res_cv, $sleep ) = @_;
        warn $sleep;
        my $t; $t = AE::timer $sleep, 0 , sub {
                undef $t;
                $res_cv->result("$sleep done!");
            };
    },
);

AE::cv->recv;

client側

my $work_count = 0;
my @sleeps = qw/1 5 2 4 3 2 1/;
my $worked = 0;

my $client = AnyEvent::MPRPC::Client->new(
    host => '127.0.0.1',
    port => 4423,
);

for my $sleep ( @sleeps ) {
    my $n = $work_count++;
    $client->call( 'sleep' => $sleep )->cb( sub {
        my $res = shift->recv;
        warn "$n: $sleep: $res";
        $worked++;
    });
};

my $w; $w = AnyEvent->timer(
    after    => 0,
    interval => 1,
    cb       => sub {
        if ($worked ==  @sleeps) {
            exit;
        }
    }
);

AE::cv->recv;

実行結果

% perl anyevent_mprpc_client.pl0: 1: 1 done! at anyevent_mprpc_client.pl line 20.
6: 1: 1 done! at anyevent_mprpc_client.pl line 20.
2: 2: 2 done! at anyevent_mprpc_client.pl line 20.
5: 2: 2 done! at anyevent_mprpc_client.pl line 20.
4: 3: 3 done! at anyevent_mprpc_client.pl line 20.
3: 4: 4 done! at anyevent_mprpc_client.pl line 20.
1: 5: 5 done! at anyevent_mprpc_client.pl line 20.
created by blog.nomadscafe.jp

AnyEvent::Gearman::Worker

ちょっと勘違いしていた。pipeliningのようなことができるわけじゃないから、1つの接続で

非同期に処理をしてくれるわけじゃない。


これは結果、普通のworkerとほぼ同じになる

use AnyEvent::Gearman::Worker;

my $worker = AnyEvent::Gearman::Worker->new(
    job_servers => ['127.0.0.1:4730'],
);
$worker->register_function(
    'sleep' => sub {
        my $job = shift;
        my $sleep = $job->workload;
        warn $sleep;
        my $t; $t = AnyEvent->timer(
            after => $sleep,
            cb    => sub {
                warn "$sleep done!";
                undef $t;
                $job->complete("$sleep done!");
            }
        );
    }
);
AE::cv->recv;

非同期/パラレルを狙うならこうする(力技

my @workers;
for ( 1 .. 4 ) {
    my $worker =
      AnyEvent::Gearman::Worker->new( job_servers => ['127.0.0.1:4730'], );
    push @workers, $worker;
    $worker->register_function(
        'sleep' => sub {
            my $job   = shift;
            my $sleep = $job->workload;
            warn $sleep;
            my $t;
            $t = AnyEvent->timer(
                after => $sleep,
                cb    => sub {
                    warn "$sleep done!";
                    undef $t;
                    $job->complete("$sleep done!");
                }
            );
        }
    );
}

AE::cv->recv;
created by blog.nomadscafe.jp

<12345678910>