Catalystの練習 - じゃんけん大会を作ってみる(2)
Catalystの練習 - じゃんけん大会を作ってみるの続き
とりあえず、なんとなく形になったので、全部貼り付け。
相当ださくてヘボくておぞいかと思いますが、やってみていろいろ勉強になりました。
#ちなみにただコピーするだけでは、動かないわな(=w=;
最初にデータベースにダミーデータをいれてやんなきゃなんない。
セットアップ関数を作ればいいんだろうけど…。
テーブルは2個作る。
◆参加者データ保存用
Table: spr
id INTEGER
name VARCHAR(45) 参加者氏名
count INTEGER 何番目の
hand VARCHAR(45) 手
time INTEGER 大会番号
◆大会データ保存用
Table: competiton
id INTEGER
time INTEGER 第N回
name VARCHAR(45) 大会名
state ENUM('invited','closed','result') 開催状態
organizer VARCHAR(45) 主催者
appeal VARCHAR(512) アピール
num INTEGER 募集人数
password VARCHAR(45) 管理パスワード
テンプレートは4つ
◆index.tt
<html> <head> <title>spr-tournament</title> </head> <body> トーナメント表 [% system_message %] <table border=1> <tr><td>回</td><td>大会名</td><td>募集人数</td><td>状況</td><td>主催者</td><td>管理パスワード</td></tr> [% FOREACH i IN compe_list %] <tr> <td>[% i.time %]</td> <td>[% i.name %]</td> <td>[% i.num %]</td> <td> [% SWITCH i.state %] [% CASE 'invited' %] <form action="post" method="post"> <input type="hidden" name="time" value="[% i.time %]"> <input type="submit" value="募集中"> </form> [% CASE 'closed' %]集計中 [% CASE %] 大会終了 <form action="result" method="post"> <input type="hidden" name="time" value="[% i.time %]"> <input type="submit" value="結果発表"> </form> [% END %] </td> <td>[% i.organizer %]</td> <td><form action="admin" method="post"> <input type="hidden" name="time" value="[% i.time %]"> <input type="password" name="password" size="30"> <input type="submit" value="管理"> </form></td> </tr> [% END %] </table> <br> <a href="admin">大会の新規設置はこちら</a><br> </body> </html>
◆construct.tt
<html> <head> <title>spr-tournament/construct</title> </head> <body> <form action="admin" method="post"> <input type="hidden" name="time" value="[% time %]"> 大会名 : <input type="text" name="name" size="30" value="[% compename %]"> <br> 募集人数 : [% IF num != '' %] [% num %] <br> [% ELSE %] <input type="text" name="num" size="30" value="[% num %]"> <br> [% END %] 主催者 : <input type="text" name="organizer" size="30" value="[% organizer %]"> <br> パスワード: <input type="password" name="password" size="30"> <br> [% IF state != '' %] 募集状況 : <select name="state"> <option value=[% state %] selected>[% state %] <option value="closed">集計中 <option value="result">結果表示 </select> <br> [% END %] アピール : <input type="text" name="appeal" size="30" value="[% appeal %]"> <br> <input type="submit" value="登録"> </form> <a href="./">トップページへもどる</a><br> </body> </html>
◆post.tt
<html> <head> <title>spr-tournament/post</title> </head> <body> 第[% time %]回大会「[% compename %]」への参加申し込み <form action="post" method="post"> <input type="hidden" name="time" value="[% time %]"> <br> NAME: <input type="text" name="name" size="30"> <br> [% count = 0 %] [% WHILE count < 10 %] HAND[% count %]: <select name="hand[% count %]"> <option value="" selected>--選択してください-- <option value="r">グー <option value="s">チョキ <option value="p">パー </select><br> [% count = count + 1 %] [% END %] <input type="submit" value="登録"> </form> <a href="./">トップページへもどる</a><br> </body> </html>
◆result.tt
<html> <head> <title>spr-tournament</title> </head> <body> トーナメント表 <table border=1> <tr><td>回</td><td>大会名</td><td>募集人数</td><td>状況</td><td>主催者</td></tr> [% FOREACH i IN compe_list %] <tr> <td>[% i.time %]</td> <td>[% i.name %]</td> <td>[% i.num %]名</td> <td> [% SWITCH i.state %] [% CASE 'invited' %]募集中 [% CASE 'closed' %]集計中 [% CASE %]大会終了 [% END %] </td> <td>[% i.organizer %]</td> </tr> [% END %] </table> [% tournament_graph %] <br> [% score %] <a href="./">トップページへもどる</a><br> </body> </html>
あとはRoot.pmに下記のように書く。
sub default : Private { my ( $self, $c ) = @_; $c->forward('make_competition_list'); # 大会情報 $c->forward('make_member_list'); # 登録者名簿をstashに格納 $c->forward('make_hand_list'); # じゃんけんデータをstashに格納 # $c->forward('tournament'); # 勝敗表の作成 $c->stash->{template} = 'index.tt'; $c->forward('spr::View::TT'); } sub begin : Private { my ( $self, $c ) = @_; $c->stash->{'judge'} = ({ "ss" => 'draw', # チョキxチョキ "sp" => 'player1', # チョキxパー "sr" => 'player2', # チョキxグー "ps" => 'player2', # パー xチョキ "pp" => 'draw', # パー xパー "pr" => 'player1', # パー xグー "rs" => 'player1', # グー xチョキ "rp" => 'player2', # グー xパー "rr" => 'draw' # グー xグー }); $c->stash->{time} = $c->req->param('time'); } # 参加登録 sub post : Global { my ( $self, $c ) = @_; $c->forward('make_member_list'); # 登録者名簿をstashに格納 if( $c->req->param('name') ne "" && $c->req->param('hand0') ne "" && $c->req->param('time') ne "" ) { # 登録情報があれば、大会参加情報の登録 $c->forward('check_duplication'); # 登録者重複チェック my @data = spr::Model::CDBI::Competition->search_like( time => $c->req->param('time') ); # $c->stash->{'test'} = "test"; if( $data[0]->get('num') > $#{$c->stash->{'member'}} + 1 && $c->stash->{dupe} ne "dupe"){ # データベースに登録 for(my $i = 0; $i < 10; $i++){ last if ($c->req->param("hand$i") eq ""); spr::Model::CDBI::Spr->insert({ name => $c->req->param('name'), count => $i, hand => $c->req->param("hand$i"), time => $c->req->param('time') }); } if ( $data[0]->get('num') == $#{$c->stash->{'member'}} + 2){ # なんか+2ってかっこ悪いな… $data[0]->set( state => 'closed'); # "募集〆切ました"を記録 } $c->stash->{system_message} = '参加登録を受け付けました。'; } else{ # 登録できません $c->stash->{system_message} = '参加登録できませんでした。'; } $c->stash->{template} = 'index.tt'; # redirectすると、stashがリセットされる $c->forward('spr::View::TT'); # } else{ # 登録情報がなければ、大会参加登録画面を表示 my @times = spr::Model::CDBI::Competition->search_like( time => $c->req->param('time') ); $c->stash->{compename} = $times[0]->get('name'); $c->stash->{template} = 'post.tt'; $c->forward('spr::View::TT'); } } # 重複登録のチェック sub check_duplication : Private { my ( $self, $c ) = @_; $c->stash->{dupe} = ''; foreach my $name ( @{$c->stash->{'member'}} ){ if( $name eq $c->req->param('name') ){ $c->stash->{dupe} = 'dupe'; } } } # 大会情報一覧の作成 sub make_competition_list : Private { my ( $self, $c ) = @_; my @compe = reverse spr::Model::CDBI::Competition->retrieve_all; $c->stash->{'compe_list'} = \@compe; } # 関数長くなってしまった… sub admin : Global { my ( $self, $c ) = @_; my $auth; my @data; if( $c->req->param('time') ne "" ){ # 第○回の情報がきてれば、開催告知済みの大会の情報更新とみなす @data = spr::Model::CDBI::Competition->search( time => $c->req->param('time') ); # 主催者認証 $auth = ( $data[0]->get('password') eq crypt( $c->req->param('password'), $data[0]->get('password') ) ) ? "OK" : "NG"; if( $auth eq "OK" ){ if( $c->req->param('name') ne "" && $c->req->param('organizer') ne "" && $c->req->param('state') ne "" ) { # 変更内容があれば、大会情報の変更登録 $data[0]->set( name => $c->req->param('name') ); $data[0]->set( organizer => $c->req->param('organizer') ); $data[0]->set( appeal => $c->req->param('appeal') ); $data[0]->set( state => $c->req->param('state') ); $c->stash->{template} = 'index.tt'; $c->forward('spr::View::TT'); } else{ # 変更内容がなければ、主催者設定画面を表示 $c->stash->{time} = $data[0]->get('time'); $c->stash->{compename} = $data[0]->get('name'); $c->stash->{num} = $data[0]->get('num'); $c->stash->{organizer} = $data[0]->get('organizer'); $c->stash->{appeal} = $data[0]->get('appeal'); $c->stash->{state} = $data[0]->get('state'); $c->stash->{template} = 'construct.tt'; $c->forward('spr::View::TT'); } } else{ # $c->stash->{system_message} = '主催者認証に失敗しました。'; $c->stash->{template} = 'index.tt'; $c->forward('spr::View::TT'); } } else{ # 新規大会の登録 if( $c->req->param('name') ne "" && $c->req->param('num') ne "" && $c->req->param('organizer') ne "" && $c->req->param('appeal') ne "" && $c->req->param('password') ne "" ) { # 登録情報があれば、大会情報の登録 # 大会番号の取得(DB的にピンポイントで取れそうな気がするけど、やり方がわからなかったので強引に) my @times = spr::Model::CDBI::Competition->retrieve_from_sql(qq{ num > 0 ORDER BY time DESC }); my $time = $times[0]->get('time') + 1; # データベースに登録 spr::Model::CDBI::Competition->insert({ name => $c->req->param('name'), num => $c->req->param('num'), organizer => $c->req->param('organizer'), appeal => $c->req->param('appeal'), password => crypt( $c->req->param('password'), "ab" ), state => 'invited', time => $time }); $c->res->redirect('/'); } else{ # 登録情報がなければ、大会設定登録画面を表示 $c->stash->{template} = 'construct.tt'; $c->forward('spr::View::TT'); } } } # 大会結果の表示 sub result : Global { my ( $self, $c ) = @_; $c->stash->{time} = $c->req->param('time'); my @competition_data = spr::Model::CDBI::Competition->search_like( time => $c->stash->{time} ); if ( $competition_data[0]->get('state') eq "result" ){ # 大会結果発表 $c->forward('make_member_list'); # 登録者名簿をstashに格納 $c->forward('make_hand_list'); # じゃんけんデータをstashに格納 $c->forward('tournament'); # 勝敗表の作成 $c->stash->{'compe_list'} = \@competition_data; } elsif ( $competition_data[0]->get('state') eq "invited" ){ # 参加者受付中 $c->stash->{tournament_graph} =<<EOM; 現在、参加者受付中です。 <form action="post" method="post"> <input type="hidden" name="time" value="$c->stash->{time}"> <input type="submit" value="参加登録画面へ"> </form> EOM } elsif ( $competition_data[0]->get('state') eq "closed" ){ # 受付〆切ました $c->stash->{tournament_graph} =<<EOM; 現在、大会結果集計中です。 EOM } else{ # 大会データが存在しません $c->stash->{tournament_graph} =<<EOM; 大会データが存在しません。 EOM } $c->stash->{template} = 'result.tt'; $c->forward('spr::View::TT'); } sub match : Private { my ( $self, $c ) = @_; my $i; # my $head; my $count1 = 0; my $count2 = 0; my $player1 = $c->stash->{'player1'}; my $player2 = $c->stash->{'player2'}; my ($hand1, $hand2, $index); # 勝敗表作成用テンポラリ my $match = ++$c->stash->{'num_of_match'}; $c->stash->{'winner'} = $player1; # 最後まであいこなら、player1勝利 $c->stash->{'score'} .= "<a name=\"$match\">第$match試合</a> <a href=\"\#\">↑topへ</a><table border=1 cellspacing=0 cellpadding=0>\n"; # 勝敗表の作成 $head = "<tr><td>名前</td>"; $hand1 = "<tr><td>$player1</td>"; $hand2 = "<tr><td>$player2</td>"; for ($i = 1; $i < 11; $i++){ my $a = $c->stash->{'hand'}->{"$player1"}->["$count1"]; my $b = $c->stash->{'hand'}->{"$player2"}->["$count2"]; $head .= "<td>$i</td>"; $hand1 .= "<td>$count1:$a</td>"; $hand2 .= "<td>$count2:$b</td>"; if ($a ne $b){ $c->stash->{'winner'} = ($c->stash->{'judge'}->{"$a$b"} eq 'player1') ? $player1 : $player2; last; } $count1 = ($count1 < $#{$c->stash->{'hand'}->{"$player1"}}) ? $count1+1 : 0; # 次の手がなければ、先頭に戻る(Player1) $count2 = ($count2 < $#{$c->stash->{'hand'}->{"$player2"}}) ? $count2+1 : 0; # 次の手がなければ、先頭に戻る(Player2) } $head .= "</tr>\n"; $hand1 .= "</tr>\n"; $hand2 .= "</tr>\n"; $c->stash->{'score'} .= $head . $hand1 . $hand2 ."</tr></table><br>\n"; } # ここまできたら、(データベースからstashに読み込んでおく)関数でくくれる気がする sub make_hand_list :Private { # データベースからstashに読み込んでおく my ( $self, $c ) = @_; my @hands; my $name; my @names_hand; my ($i, $j); # ループカウンタ my %hand_list; # { 名前番号 => 出す手 } # 登録者名簿の名前の数だけループを回す for ($j = 0; $j < $#{$c->stash->{'member'}} + 1; $j++){ my @names_hand = (); $name = $c->stash->{'member'}[$j]; @hands = spr::Model::CDBI::Spr->search_like(name => $name, time => $c->stash->{time}, { order_by => 'count'}); for($i = 0; $i < $#hands + 1; $i++){ $names_hand[$i] = $hands[$i]->get('hand'); } $hand_list{"$name"} = [ @names_hand ]; } $c->stash->{'hand'} = \%hand_list; } sub make_member_list : Private { # データベースからstashに読み込んでおく my ( $self, $c ) = @_; my $name; my $i = 0; # ループカウンタ my %name_to_num; my @member; my $it = spr::Model::CDBI::Spr->search_like( count => 0, time => $c->stash->{time} ); # イテレータのit while(my $member = $it->next){ $member[$i] = $member->name; $i++; } for($i = 0; $i < $#member + 1; $i++){ $name_to_num{$member[$i]} = $i; } $c->stash->{'member'} = \@member; # 登録者名簿( 名前 )のリスト $c->stash->{'name_to_num'} = \%name_to_num; # 登録者名簿{ 名前 => 番号 }のハッシュ } sub tournament : Private { # トーナメント表の作成 my ( $self, $c ) = @_; my $max = 2; my $num_of_members = $#{$c->stash->{'member'}} + 1; my $lvl; my ($i, $j); # ループカウンタ my $n; my $p; my $wi; my $wn; my @cell; my $player1; my $player2; my $match; for( $lvl=1; $max <= $num_of_members; $lvl++){ $max *= 2; } # 参加人数をこえる最小の2^nを求める my $seads = $max - $num_of_members; # シード選手の人数 = (参加人数をこえる最小の2^n) - 参加人数 my $halfseads = int( $seads / 2 ); # シード数の1/2 my $restseads = $num_of_members - ( $seads - $halfseads ); # 残りのシード数 # セルの初期化 for( $j = 1; $j <= $lvl * 2; $j++ ){ for( $i = 0; $i < $num_of_members * 2; $i++ ){ $cell[$j][$i] = ""; } } $p = 0; # 表示内容を配列@cellに格納する for( $i = 0; $i < $num_of_members; $i++ ){ # Level 1の表示内容決定(初戦のみシード枠判定がある) $wi = $i + 1; if( $i < $halfseads || $i >= $restseads){ $cell[1][$i*2] = "─"; # 参加者が2^nの場合、全員初戦シード扱いになる $cell[2][$i*2] = $c->stash->{'member'}[$i]; } else{ if( $p == 0 ){ # 対戦アーチ描画中でなければ $cell[1][$i*2] = "┐"; # $p = 1; # 対戦アーチ描画中フラグON } else{ $cell[1][$i*2] = "┘"; # $cell[1][$i*2-1] = "├"; # $c->stash->{'player1'} = $c->stash->{'member'}[$i-1]; # $c->stash->{'player2'} = $c->stash->{'member'}[$i]; # $c->forward('match'); # 「じゃんけんぽんっ」 $cell[2][$i*2-1] = $c->stash->{'winner'}; $match = $c->stash->{'num_of_match'}; $cell[1][$i*2-1] .= "<a href=\"\#$match\">" . $match . "</a>"; # $p = 0; # 対戦アーチ描画中フラグOFF } } } for( $j = 3; $j <= $lvl * 2; $j = $j+2 ){ # Level 2以上の表示内容決定 $p = 0; for( $i = 0; $i < $num_of_members * 2; $i++ ){ if( $cell[$j-1][$i] ne "" ){ # 一段下のセルが空白でなければ描画開始 if( $p == 0 ){ # 対戦アーチ描画中でなければ $cell[$j][$i] = "┐"; # $c->stash->{'player1'} = $cell[$j-1][$i]; $p = 1; # 対戦アーチ描画中フラグON $n = 1; # 対戦アーチの長さを測定開始 } else{ $cell[$j][$i] = "┘"; # $c->stash->{'player2'} = $cell[$j-1][$i]; $wn = int( $n / 2 ); # 対戦アーチ描画終了時に、真ん中を"├"に変更 $cell[$j][$i-$wn] = "├"; # $c->forward('match'); # 「じゃんけんぽんっ」 $cell[$j+1][$i-$wn] = $c->stash->{'winner'}; $match = $c->stash->{'num_of_match'}; $cell[$j][$i-$wn] .= "<a href=\"\#$match\">" . $match . "</a>"; # $p = 0; # 対戦アーチ描画中フラグOFF } } elsif( $p == 1 ){ $cell[$j][$i] = "│"; # $n++; # 対戦アーチの長さを測定する } } } # 表示内容をstash->{'tournament_graph'}に格納する $c->stash->{'tournament_graph'} = "<table border=0 cellspacing=0 cellpadding=0>\n"; for( $i = 0; $i < $num_of_members * 2; $i++){ # 縦幅(セル数)は、参加者*2マス $c->stash->{'tournament_graph'} .= "<TR>"; for( $j = 0; $j <= $lvl * 2; $j++){ # 最下位Levelから出力 $c->stash->{'tournament_graph'} .= "<TD>"; if( $j == 0 ){ # Level 0は参加者表示欄 if( $i % 2 == 0 ){ # 偶数欄に参加者No.を表示 $wi = int( $i / 2 ); # my $name = $c->stash->{'member'}[$wi]; $c->stash->{'tournament_graph'} .= $c->stash->{'member'}[$wi]; } } else{ # Level 1以上 if($cell[$j][$i] ne ""){$c->stash->{'tournament_graph'} .= $cell[$j][$i];} # その他セルは$cell[$i][$j]=0のため、何も出力しない(空白セル) } $c->stash->{'tournament_graph'} .= "</TD>\n"; } $c->stash->{'tournament_graph'} .= "</TR>"; } $c->stash->{'tournament_graph'} .= "</table>"; }