美容室でソーシャルゲームの話

昨日、美容室に行って髪を切った。その時の話。
アシスタントの子にシャンプーしてもらいながら、GREE等の携帯ソーシャルゲームの話になった。
その子はGREEの携帯ゲームをよくやるそうだ。
自分はその手のソーシャルゲームの開発を何度かした事があると言うと、ソーシャルゲームを地下鉄でも遊べるようにして欲しいと言われた。
地下鉄で電波が届かないのはドコモやソフトバンクの責任なのでどうしようもないよ、と答えたが、そのアシスタントの子によれば、ゲームしてる途中で電波が途切れて、しばらくして電波が復帰しても、それまで遊んでたゲームのポイント等が消滅してるのが許せないらしい。
ソーシャルゲームと言ってもそれぞれだけれど、一般的には、ウェブサイト中のあるページに行くとFlashが携帯ブラウザにダウンロードされ、ゲームが始まる。
ゲームを何度かプレーし、ゲームが終わると、その結果がページに表示されポイントがもらえたりする。
このゲームをプレーしてる最中だけれど、ゲームの勝ち負け判定等はサーバ側で処理し、携帯ブラウザには勝ち負けの結果等の結果表示に必要な最低限のデータだけ渡される。
なので、重要なデータはすべてサーバ側で保持していて、Flashでは新たにデータを作る事はない、というのがソーシャルゲームでは一般的だと思う。
携帯ブラウザのFlashで複雑な処理を行うのが難しいのでそういう構成が多い。
話をアシスタントの子に戻すと、データはサーバ側で保持しているので、ゲーム途中で電波が途切れたとしても、サーバ側にはデータが残ってる。
携帯ブラウザが再接続して来た時には、このデータを再利用してゲームを続行する、もしくはもう一度Flashをダウンロードさせてその際にそのデータを埋め込んでおけばよい。
ただし、こういう処理を要件に含めるのは面倒だし、ユーザの意図的な不正行為と見分けるのが難しい為、あまりそのような事をしてるソーシャルゲームはないんだと思う。
ソーシャルゲームって、今流行ってるけれど、ソーシャルゲームの開発者はあまりソーシャルゲームで遊んでなかったりするので、ユーザ目線が足りてないなと思う。

PHPカンファレンス2010

PHPカンファレンス2010に行ってきた。
朝から晩までパイプイスに座ってたんで、家に帰ってからも疲れてた。
PerlやPHPのセミナー・勉強会にたまに行くけど、インフラの話が多い。
うちのサイトにはこんだけたくさんのアクセスがあるから、MySQLをレプリケーションさせて、キャッシュサーバを導入して、アプリケーションサーバをスケール展開させて、みたいな話。
今回のPHPカンファレンスもそうだった。
インフラ周りのノウハウ紹介みたいなのもタメになるなとは思うけど、もう少しアーキテクト、システム開発手法についての発表があってもいいのでは?
エンタープライジーな業務アプリを作ってる人、JavaやASP.NETではそういう話題も多いと思う。
例えば、この2ちゃんねるのスレッド「ドメインモデルVSトランザクションスクリプト」なんてエキサイティングで面白い。
こういうのを公開の場で、朝まで生テレビみたいな感じでやれば、かなり盛り上がりそう。
自分は昔・プロレス、今・総合格闘技を見るのが好きなんだけれど、実戦で最強は誰か?というのは定番の話題。
候補に挙がるのは、ブロック・レスナー(現UFCヘビー級王者)、マイク・タイソンアレクサンドル・カレリン(総合格闘技以外のメジャー格闘技の近年最強チャンピオン)、アフリカゾウグリズリー(陸上生物最大級)、ダイオウイカマッコウクジラ(水上生物最大級)、ムツゴロウさん(対野生生物最強)、バラク・オバマ(アメリカ合衆国大統領)あたり。
いろんな観点から語る事が出来、評価する事が出来、議論が尽きないというのが、システム開発手法に通じると思う。

PHPでページャークラスを作ってみた

ページャークラスを作ってみた。
特徴は、
・「前へ」「次へ」「最初へ」「最後へ」の相対表現のリンク、全ページ番号への直接リンク、全ページ数、現在表示中の項目の番号に対応。
・絞り込み検索等でもページング出来るようにGETのクエリパラメータを保持出来る。
・メソッドチェーンで直感的(?)にページ情報を取り出せる。
です。

使い方は下のようにPagerクラスのインスタンス$pagerを作り、
[php]
<?php
$pager = new Pager(array(
‘cur_no’ => 2, // (1)
‘total’ => 45, // (2)
‘num_per_page’ => 10, // (3)
‘path’ => ‘http://abc.com/index.php?flg=1’, // (4)
‘query’ => array(‘year’ => 2010, ‘month’ => 9) // (5)
));
[/php]

コンストラクタの引数
(1) 現在のページ番号
(2) 全項目数
(3) 1ページに表示する項目数
(4) ページのURL
(5) クエリーパラメータに加えたいハッシュ情報

↓のようにHTML内で$pagerからページ情報を取り出す。例えば「次のページ」のURLは$pager->next()->url()で、ページ番号は$pager->next()->no()のようにして。
[php]
<ul>
<li><a href="<?= $pager->first()->url() ?>">最初へ</a></li>
<li><a href="<?= $pager->prev()->url() ?>">前へ</a></li>
<li>
<ul>
<?php foreach ($pager->all() as $p) { ?>
<?php if ($pager->no() == $p->no()) { ?>
<li><?= $p ?>ページ</li>
<?php } else { ?>
<li><a href="<?= $p->url() ?>"><?= $p ?>ページへ</a></li>
<?php } ?>
<?php } ?>
</ul>
</li>
<li><a href="<?= $pager->next()->url() ?>">次へ</a></li>
<li><a href="<?= $pager->last()->url() ?>">最後へ</a></li>
<li>全<?= $pager->last() ?>ページ中 <?= $pager->cur() ?>ページ 表示</li>
<li>全<?= $pager->total() ?>項目中 <?= $pager->cur()->firstIndex() ?>から<?= $pager->cur()->lastIndex() ?>番目の項目 表示</li>
</ul>
[/php]

これで下のようなHTMLになる。
[html]
<ul>
<li><a href="abc.com/index.php?flg=1&year=2010&month=9&no=1">最初へ</a></li&gt;
<li><a href="abc.com/index.php?flg=1&year=2010&month=9&no=1">前へ</a></li&gt;
<li>
<ul>
<li><a href="abc.com/index.php?flg=1&year=2010&month=9&no=1">1ページへ</a></li&gt;
<li>2ページ</li>
<li><a href="abc.com/index.php?flg=1&year=2010&month=9&no=3">3ページへ</a></li&gt;
<li><a href="abc.com/index.php?flg=1&year=2010&month=9&no=4">4ページへ</a></li&gt;
<li><a href="abc.com/index.php?flg=1&year=2010&month=9&no=5">5ページへ</a></li&gt;
</ul>
</li>
<li><a href="abc.com/index.php?flg=1&year=2010&month=9&no=3">次へ</a></li&gt;
<li><a href="abc.com/index.php?flg=1&year=2010&month=9&no=5">最後へ</a></li&gt;
<li>全5ページ中 2ページ 表示</li>
<li>全45項目中 11から20番目の項目 表示</li>
</ul>
[/html]

ページャークラスのソースコードはこれ↓。
[php]
class Pager {
private $total;
private $num_per_page;
private $cur_no;
private $first_no;
private $last_no;
private $next_no;
private $prev_no;
private $query = array();
private $no;

function Pager($params) {
$total = $params[‘total’];
$num_per_page = $params[‘num_per_page’];
$cur_no = $params[‘cur_no’];
$path = isset($params[‘path’]) ? $params[‘path’] : ”;
$query = isset($params[‘query’]) ? $params[‘query’] : array();

$first_no = 1;
$last_no = ceil($total / $num_per_page);
$next_no = $cur_no < $last_no ? $cur_no + 1 : null;
$prev_no = $cur_no > $first_no ? $cur_no – 1 : null;
$all_nos = array();
for ($i = $first_no; $i <= $last_no; $i++) {
$all_nos[] = $i;
}
$this->total = $total;
$this->num_per_page = $num_per_page;
$this->cur_no = $cur_no;
$this->first_no = $first_no;
$this->last_no = $last_no;
$this->next_no = $next_no;
$this->prev_no = $prev_no;
$this->all_nos = $all_nos;
$this->path = $path;
$this->query = $query;
$this->no = $cur_no;
}

function all() {
$buf = array();
$all_nos = $this->all_nos;
foreach ($all_nos as $no) {
$buf[] = new Pager(array(
‘cur_no’ => $no,
‘total’ => $this->total,
‘num_per_page’ => $this->num_per_page,
‘path’ => $this->path,
‘query’ => $this->query
));
}
return $buf;
}

function cur() {
$this->no = $this->cur_no;
return $this;
}

function total() { return $this->total; }

function first() {
$this->no = $this->first_no;
return $this;
}

function last() {
$this->no = $this->last_no;
return $this;
}

function next() {
$this->no = $this->next_no;
return $this;
}

function prev() {
$this->no = $this->prev_no;
return $this;
}

function no() {
return $this->cur_no;
}

function firstIndex() {
$idx = $this->num_per_page * ($this->no – 1) + 1;
if ($idx > $this->total) $idx = $this->total;
return $idx;
}

function lastIndex() {
$idx = $this->num_per_page * $this->no;
if ($idx > $this->total) $idx = $this->total;
return $idx;
}

function url($ssl = false) {
if (!$this->no) return ”;
$url_info = parse_url($this->path);
if ($ssl) $url_info[‘scheme’] = ‘https’;
$query = array();
if (isset($url_info[‘query’]) && $url_info[‘query’] != ”) {
parse_str($url_info[‘query’], $query);
}
$query = array_merge($query, $this->query);
$query[‘no’] = $this->no;
$buf = array();
foreach ($query as $k => $v) {
$buf[] = sprintf(‘%s=%s’, urlencode($k), urlencode($v));
}
$url_info[‘query’] = join(‘&’, $buf);
if (isset($url_info[‘scheme’]) &&isset($url_info[‘host’]) ) {
$url = sprintf(‘%s://%s%s%s%s’,
$url_info[‘scheme’],
$url_info[‘host’],
$url_info[‘path’],
$url_info[‘query’] ? ‘?’ . $url_info[‘query’] : ”,
isset($url_info[‘fragment’]) && $url_info[‘fragment’] != ” ? ‘#’ . $url_info[‘fragment’] : ”
);
} else {
$url = sprintf(‘%s%s%s’,
$url_info[‘path’],
$url_info[‘query’] ? ‘?’ . $url_info[‘query’] : ”,
isset($url_info[‘fragment’]) && $url_info[‘fragment’] != ” ? ‘#’ . $url_info[‘fragment’] : ”
);
}
return $url;
}

function __toString() {
return (string) $this->no;
}
}
[/php]