#!/usr/bin/perl ############################################################ # 人狼怪奇ファイル written by aminisi(05/3/6+9/22) # ############################################################ use strict qw(vars subs); use warnings; use vars qw($ScriptName $LockFile $dataDirectory $dataFilePref $chatFilePref $boardFilePref $AdminPass); use vars qw(%in @Log $Day $ID $PASS $VillageNm $VillageName %Char $LoginFlag $EndFlag); ($ID,$PASS,$LoginFlag,$EndFlag)=('')x4; #===========================================================# my $errorFlag; $SIG{__WARN__} = sub { print "Content-Type: text/html\n\n" unless $errorFlag; $errorFlag=1; print @_; }; $SIG{__DIE__} = sub { print "Content-Type: text/html\n\n" unless $errorFlag; $errorFlag=1; print "\n==========died===========\n"; print @_; }; #===========================================================# require 'Output.pl' or die "Content-Type: text/html\n\nCant read Output.pl"; # ◆初期設定◆ my $Lock=0; $ScriptName='jinro.cgi'; $LockFile ='Lock.cgi'; $dataDirectory='Data/'; $dataFilePref='Data_'; $chatFilePref='Chat_'; $boardFilePref='brd_'; $AdminPass='temp'; # ○暫定(複数村に対応するかは未定)○ $VillageNm=0; #◆入力デコード◆ @in{qw(ID PASS Admin Action Target think Message Icon Position Side EntryID Name Role ajax Last Day All)}=('')x17; { my ($Query,@Query); if ($ENV{'REQUEST_METHOD'} eq 'GET') { $Query=$ENV{'QUERY_STRING'} } else { read(STDIN,$Query,$ENV{'CONTENT_LENGTH'}) } foreach (split(/&/,$Query)) { tr/+/ /; my ($Key,$Value)=split(/=/); $Key =~ s/%([A-Fa-f0-9][A-Fa-f0-9])/pack("c",hex($1))/ge; $Value=~ s/%([A-Fa-f0-9][A-Fa-f0-9])/pack("c",hex($1))/ge; $Value =~s/&/&/g; $Value =~s/>/>/g; $Value =~s//g; } else { $Value =~s/\x0D\x0A|\x0D|\x0A//g; } $in{$Key}=$Value; } } #◆事前処理◆ # ○認証 if($in{'Action'} ne 'TOP') { if($in{'ID'}) { $ID=$in{'ID'}; $PASS=$in{'PASS'} } else { ($ID,$PASS)=($ENV{'HTTP_COOKIE'}=~/jinro=([^_]*)_([^;]*)/); } $LoginFlag=&Login($ID,$PASS); &error('パスワードが違うか登録されていません') if !$LoginFlag and $in{'ID'}; } # ○初期化 if($in{'Action'} eq 'Format' and $in{'Admin'} eq $AdminPass) { &FormatGame(); } &Lock(); &LoadData(); #-------------- #◆◆処理分岐(ディスパッチテーブル)◆◆ my ($RenewFlag,@NewLog); # ○エントリー if($Day==0 and $in{Action} eq 'Entry') { &Entry(); } # ◇プレイヤー行動処理◇ elsif($LoginFlag and $in{'Action'}) { #○エピローグ時 if($EndFlag) { if($in{'Action'} eq 'Speak') { &DoSpeak(); } elsif($in{'Action'} eq 'Format') { &FormatGame(); } } #○通常日 elsif($Day>0 and $Char{$ID}{'State'} ne 'Dead') { $in{'Action'}='Memo' if $in{'think'}; my %Roles= ( 'Villager' =>{'Speak'=>1,'Vote2'=>1,'VoteCancel'=>-1,'Vote'=>2,'Memo'=>1}, 'WereWolf' =>{'Speak'=>1,'Vote2'=>1,'VoteCancel'=>-1,'Vote'=>2,'Memo'=>1,'Kill'=>2,'Telepathy'=>1}, 'Shaman' =>{'Speak'=>1,'Vote2'=>1,'VoteCancel'=>-1,'Vote'=>2,'Memo'=>1,'Incantation'=>1}, 'Spiritualism'=>{'Speak'=>1,'Vote2'=>1,'VoteCancel'=>-1,'Vote'=>2,'Memo'=>1}, 'Hunter' =>{'Speak'=>1,'Vote2'=>1,'VoteCancel'=>-1,'Vote'=>2,'Memo'=>1,'Defence'=>2}, 'Lunatic' =>{'Speak'=>1,'Vote2'=>1,'VoteCancel'=>-1,'Vote'=>2,'Memo'=>1}, 'Esper' =>{'Speak'=>1,'Vote2'=>1,'VoteCancel'=>-1,'Vote'=>2,'Memo'=>1} ); my $value=$Roles{$Char{$ID}{'Role'}}->{$in{'Action'}}; if( $value>0 and $Day>=$value or $value<0 and $Day==$value*-1 ) { if(defined(&{'Do'.$in{'Action'}})) { &{'Do'.$in{'Action'}}; } } } # ○初日 elsif($Day==0) { if($in{'Action'} eq 'Speak') { &DoSpeak(); } elsif($in{'Action'} eq 'Start' and keys(%Char)>=6) { $RenewFlag=1; } } # ○死亡時 else { if($in{'Action'} eq 'Groan') { &DoGroan(); } } } # ◆更新判定◆ &Renew() if $RenewFlag; # ◆データ保存◆ &SaveData(); &AddLog() if @NewLog; # ◆出力(外部ファイル)◆ &LoadLog(); &Output(); # &Output2( # 'in' =>\%in, # 'Char'=>\%Char, # 'Log' =>\@Log, # 'ID' =>$ID, # 'PASS'=>$PASS, # 'Day' =>$Day, # 'LoginFlag' =>$LoginFlag, # 'EndFlag' =>$EndFlag, # 'VillageName'=>$VillageName # ); #■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ #◆アクション処理◆ # ○発言系 sub DoSpeak { return if &AddMessage('MES'); #board.cgi用処理 if($in{'board'}) { open(OUT,'>>'.$dataDirectory.$boardFilePref.$VillageNm.'.cgi') or &error('データファイルが開けませんでした'); #Type Day Time ID Message foreach(@NewLog) { print OUT "$ID,$in{'Message'},".time()."\n"; } close OUT; } } #--------- sub DoMemo { AddMessage('MEMO'); } #--------- sub DoTelepathy { AddMessage('WIS'); } #--------- sub DoGroan { AddMessage('GROAN'); } sub AddMessage { $Char{$ID}{'Icon'}=$in{'Icon'}+0; return 1 if $in{'Message'}=~/^\s*$/; my $p = $in{Position}+0; $p=0 if $p<0 or 2<$p; push(@NewLog, { 'Type'=>shift, 'Day' =>$Day, 'Time'=>time(), 'ID' =>$ID, 'Message'=>$in{'Message'}, 'Position'=>$p, 'Side'=>($in{'Side'}?1:0) } ); #test用 return 0; } #----------------------------- # ○行動系 sub DoVote { &SetData('Vote'); # -更新チェック- foreach my $C(values %Char) { if ($C->{'State'} ne 'Dead' and $C->{'Vote'} eq '') { return; } } $RenewFlag=1; } #DoVoteと統合予定 sub DoVote2 { $Char{$ID}{'Vote'}='1'; # -更新チェック- foreach my $C(values %Char) { if ($C->{'State'} ne 'Dead' and $C->{'Vote'} eq '') { return; } } $RenewFlag=1; } # ---- sub DoVoteCancel { $Char{$ID}{'Vote'}=''; } # ---- sub DoKill { my $TargetID=&GetTargetID($in{'Target'}); if( !$TargetID or $Char{$TargetID}{'State'}eq 'Dead' or $Char{$TargetID}{'Role'} eq 'WereWolf' ) { &error('入力が不正です'); } $Char{$ID}{'Target'}=$TargetID; } # ---- sub DoDefence { &SetData('Target'); } # ---- sub DoIncantation { &SetData('Target'); #初日の更新処理 if($Day==1) { $Char{$ID}{'Vote'}='1'; foreach my $C(values %Char) { if ($C->{'State'} ne 'Dead' and $C->{'Vote'} eq '') { return; } } $RenewFlag=1; } } #--------------------------------------- sub SetData { my $TargetID=&GetTargetID($in{'Target'}); if( !$TargetID or ($Char{$TargetID}{'State'} eq 'Dead') or $ID eq $TargetID ) { &error('入力が不正です'); } $Char{$ID}{shift()}=$TargetID; } #================================================= #◆エントリー処理◆ sub Entry { if( $in{'EntryID'} eq '' or $in{'EntryID'} =~/_/ or $in{'Name'} eq '' or $in{'PASS'} eq '' or $in{'PASS'} =~/_/ ) { #TODO:「_」をエスケープする &error('ID,PASSが空白か使用できない文字が含まれています'); } &error('入力されたIDは使用できません') if $Char{$in{'EntryID'}}; foreach(keys %Char) { if($Char{$_}{'Name'} eq $in{'Name'}) { &error('入力された名前は使用できません'); } } $Char{$in{'EntryID'}}= { 'Name'=>$in{'Name'}, 'PASS'=>$in{'PASS'}, 'Vote'=>$in{'Role'}, 'Position'=>0 }; $ID=$in{'EntryID'}; $PASS=$in{'PASS'}; $LoginFlag=1; &AddSysMessage((keys(%Char)+0).'人目、'.$in{'Name'}.'。'); } #■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ #◆各種サブルーチン◆ # ○更新処理 sub Renew { $Day++; if($Day==1) { &AddSysMessage('さあ、自らの姿を鏡に映してみよう。
そこに移るのはただの村人か、それとも血に飢えた狼か。

例え人狼でも、多人数で立ち向かえば怖くない。
問題は、だれが人狼なのかという事だ。
占い師の能力を持つ人間ならば、それを見破れるだろう'); #役割割り振り &SetRole(); #共有者処理 &ProcEsper(); } elsif($Day==2) { #NPC死亡処理 &AddSysMessage('次の日の朝、蹴戸 が無残な姿で発見された'); &AddSysMessage( 'ついに犠牲者がでた。人狼はこの村人達のなかにいる。
' .'しかし、それを見分ける手段はない。

' .'村人達は、疑わしいものを排除するため、投票を行うことにした。
' .'無実の犠牲者が出るのもやむをえない。村が全滅するよりは……。

' .'最後まで生き残るのは村人か、それとも人狼か。' ); &ProcIncantation(); } else { #突然死…は無し #投票処理 my %Vote; my $text; foreach(keys %Char) { next if $Char{$_}{'State'} eq 'Dead'; $Vote{$Char{$_}{'Vote'}}++; $text.=$Char{$_}{'Name'}.' は '.$Char{$Char{$_}{'Vote'}}{'Name'}.' に投票した。
'; } my @Vote=sort{$Vote{$a} <=> $Vote{$b}}keys %Vote; @Vote=grep{$Vote{$_}==$Vote{$Vote[$#Vote]}}@Vote; my $Execution=$Vote[int(rand(@Vote+0))]; $Char{$Execution}{'State'}='Dead'; $text.='
'.$Char{$Execution}{'Name'}.' は村人達の手により処刑された。'; &AddSysMessage($text); #襲撃処理 &ProcKill(); #勝敗判定 &Judge(); return if $EndFlag; #能力処理 &ProcIncantation(); &ProcSpiritSpeak($Execution); } unless($Day==1) { foreach(keys %Char) { $Char{$_}{'Vote'}=''; $Char{$_}{'Target'}=''; } } #ボード初期化 open(OUT,'>'.$dataDirectory.$boardFilePref.$VillageNm.'.cgi') or &error('データファイルが開けませんでした'); close OUT; } # ○勝敗判定 sub Judge { my $human; my $wolf; foreach(keys %Char) { next if $Char{$_}{'State'} eq 'Dead'; if($Char{$_}{'Role'} ne 'WereWolf') { $human++; } else { $wolf++; } } if($wolf==0) { &AddSysMessage('全ての人狼を退治した……。人狼に怯える日々は去ったのだ!'); &Finish(); } elsif($human<=$wolf) { &AddSysMessage( 'もう人狼に対抗できるほど村人は残っていない……。' .'人狼は残った村人を全て喰らい、別の獲物を求めてこの村を去っていた。' ); &Finish(); } } # ○終了処理 sub Finish { my $text; my %Role= ( 'Villager' =>'村人', 'WereWolf' =>'人狼', 'Shaman' =>'占い師', 'Spiritualism'=>'霊能者', 'Hunter' =>'狩人', 'Lunatic' =>'狂人', 'Esper' =>'共有者' ); foreach(keys %Char) { $text.=$Char{$_}{'Name'}.' ('.$_.')、'.($Char{$_}{'State'} ne 'Dead'?'生存':'死亡').'。'; $text.=$Role{$Char{$_}{'Role'}}.'だった。
'; } AddSysMessage($text); $EndFlag=1; } # ○役の割り振り sub SetRole { # my %roles=('Lunatic'=> 1,'Shaman'=>1,'Hunter'=>1,'WereWolf'=>2); my %roles=('Shaman'=>1,'Hunter'=>1,'WereWolf'=>2); $roles{'Spiritualism'}++ if keys(%Char)>= 9; $roles{'Lunatic'}++ if keys(%Char)>=10; $roles{'WereWolf'}++ if keys(%Char)>=16; my $sum; foreach(values %roles) { $sum+=$_; } $roles{'Villager'}=keys(%Char)-$sum; my %roles2=%roles; my @charID=keys %Char; foreach ( 0..$#charID ) { my $rand=int(rand(@charID)); my $tmp=$charID[$_]; $charID[$_]=$charID[$rand]; $charID[$rand]=$tmp; } foreach(keys %roles) { foreach my $C(@charID) { if($Char{$C}{'Vote'} eq $_) { $Char{$C}{'Role'}=$_; if (--$roles{$_}==0) { delete $roles{$_}; last; } } } } foreach(keys %roles) { foreach my $C(@charID) { if($Char{$C}{'Role'} eq '') { $Char{$C}{'Role'}=$_; if (--$roles{$_}==0) { last; } } } } foreach(@charID) { if ($Char{$_}{'Role'} eq 'Shaman') { $Char{$_}{'Vote'}=''; } else { $Char{$_}{'Vote'}=1; } } &AddSysMessage("どうやらこの中には村人が$roles2{'Villager'}名、人狼が$roles2{'WereWolf'}名、" ."占い師が$roles2{'Shaman'}".($roles2{'Spiritualism'}?"、霊能者が$roles2{'Spiritualism'}名":'') .($roles2{'Lunatic'}?"、狂人が$roles2{'Lunatic'}名":'')."、狩人が$roles2{'Hunter'}名".($roles2{'Esper'}?"、共有者が$roles2{'Esper'}名":'')."いるようだ。"); } #■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ #◆能力処理◆ # ○占い sub ProcIncantation { my $PID=&SearchRole('Shaman'); my %C=%{$Char{$PID}}; my $TargetID=$C{'Target'}; if($TargetID) { if($Char{$TargetID}{'Role'} eq 'WereWolf') { &AddPowerMessage($PID,$Char{$TargetID}{'Name'}."は人狼のようだ。"); } else { &AddPowerMessage($PID,$Char{$TargetID}{'Name'}."は人間のようだ。"); } } } # ○霊能 sub ProcSpiritSpeak { my $PID=&SearchRole('Spiritualism'); return unless $PID; my %C=%{$Char{$PID}}; my $TargetID=shift; if($TargetID) { if($Char{$TargetID}{'Role'} eq 'WereWolf') { &AddPowerMessage($PID,$Char{$TargetID}{'Name'}."は人狼のようだ。"); } else { &AddPowerMessage($PID,$Char{$TargetID}{'Name'}."は人間のようだ。"); } } } # ○襲撃 sub ProcKill { my $Killer=&SearchRole('WereWolf'); my $TargetID =$Char{$Killer}{'Target'} if $Killer; my $HunterID =&SearchRole('Hunter'); my $DefenceID=$Char{$HunterID}{'Target'} if $HunterID; if($Killer and $TargetID and $Char{$TargetID}{'State'} ne 'Dead') { &AddKillMessage($Killer,$Char{$TargetID}{'Name'}.' ! 今日がお前の命日だ!'); &AddPowerMessage($HunterID,$Char{$HunterID}{'Name'}.' は '.$Char{$DefenceID}{'Name'}.' を守っている。') if $DefenceID; if($TargetID ne $DefenceID) { &AddSysMessage('次の日の朝、'.$Char{$TargetID}{'Name'}.'が無残な姿で発見された。'); $Char{$TargetID}{'State'}='Dead'; } else { &AddPowerMessage($HunterID,'人狼を撃退した!'); &AddSysMessage('今日は犠牲者がいないようだ。人狼は襲撃に失敗したのだろうか。'); } } else { &AddSysMessage('今日は犠牲者がいないようだ。人狼は襲撃に失敗したのだろうか。'); } } # ○共有者 sub ProcEsper { my @Esper; foreach(keys %Char) { if($Char{$_}{'Role'} eq 'Esper') { push @Esper,$_; } } if(@Esper==2) { foreach(0,1) { &AddPowerMessage($Esper[$_],$Char{$Esper[$_]}{'Name'}.'は'.$Char{$Esper[1-$_]}{'Name'}.'に強い繋がりを感じた'); } } } #■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ #◆サブルーチン◆ sub FormatGame { &Lock(); open(OUT,'>'.$dataDirectory.$dataFilePref.$VillageNm.'.cgi') or &error('データファイルが開けませんでした'); print OUT "0,0,Test\n"; $VillageName='Test'; $Day=0; $EndFlag=0; undef %Char; close OUT; open(OUT,'>'.$dataDirectory.$chatFilePref.$VillageNm.'.cgi') or &error('データファイルが開けませんでした'); close OUT; open(OUT,'>'.$dataDirectory.$boardFilePref.$VillageNm.'.cgi') or &error('データファイルが開けませんでした'); close OUT; &AddSysMessage( '昼は人間のふりをして、夜に正体を現すという人狼。
' .'その人狼が、この村にも紛れ込んでいるという噂が広がった。

' .'村人達は半信半疑ながらも、村はずれの宿に集められることになった。' ); } #======================================= sub LoadData { open(IN,$dataDirectory.$dataFilePref.$VillageNm.'.cgi') or &error('データファイルが開けませんでした'); chomp(($EndFlag,$Day,$VillageName)=split(/,/,,3)); while() { chomp; my($ID,@ary)=split(/,/); @{$Char{$ID}}{'PASS','Name','Role','Job','State','Vote','Target','Icon','Position'}=@ary; } close IN; } sub SaveData { #TODO:ロック処理 open(OUT,'+<'.$dataDirectory.$dataFilePref.$VillageNm.'.cgi') or &error('データファイルが開けませんでした'); flock(OUT,2); seek(OUT, 0, 0); print OUT "$EndFlag,$Day,$VillageName\n"; foreach (keys %Char) { print OUT "$_,"; print OUT join(',',@{$Char{$_}}{'PASS','Name','Role','Job','State','Vote','Target','Icon','Position'}); print OUT "\n"; } truncate(OUT, tell(OUT)); close OUT; } #======================================= sub LoadLog { #TODO:ロック処理 open(IN,$dataDirectory.$chatFilePref.$VillageNm.'.cgi') or &error('データファイルが開けませんでした'); while() { chomp; my %H; @H{'Type','Day','Time','ID','Message','Position','Side'}=split(/,/,$_); push @Log,\%H; } close IN; } sub AddLog { open(OUT,'>>'.$dataDirectory.$chatFilePref.$VillageNm.'.cgi') or &error('データファイルが開けませんでした'); #Type Day Time ID Message foreach(@NewLog) { print OUT join(',',@{$_}{'Type','Day','Time','ID','Message','Position','Side'}); print OUT "\n"; } close OUT; } #======================================= sub Login { return ($_[0] and $Char{$_[0]} and $Char{$_[0]}{'PASS'} eq $_[1]?1:0); } #======================================= # ○役職検索 sub SearchRole { my $Role=shift; foreach(keys %Char) { if($Char{$_}{'Role'} eq $Role and $Char{$_}{'State'} ne 'Dead') { return $_; } } } # ○名前をIDに sub GetTargetID { my $TargetName=shift; foreach(keys %Char) { if($Char{$_}{'Name'} eq $TargetName) { return $_; } } } #======================================= sub AddSysMessage { push(@NewLog, { 'Type'=>'SYS', 'Day' =>$Day, 'Time'=>time(), 'ID' =>'', 'Message'=>shift } ); } sub AddPowerMessage { push(@NewLog, { 'Type'=>'POW', 'Day' =>$Day, 'Time'=>time(), 'ID' =>shift, 'Message'=>shift } ); } sub AddKillMessage { push(@NewLog, { 'Type'=>'KILL', 'Day' =>$Day, 'Time'=>time(), 'ID' =>shift, 'Message'=>shift } ); } #======================================= sub Lock { return if $Lock; open (LOCK,$LockFile); flock(LOCK,2); $Lock=1; } #======================================= sub BinarySearch { my $v=shift; my @a=@_; unshift(@a, { 'Time'=>-1, } ); my $mid; my $low=1; my $high=$#a; while($low<=$high) { $mid=int(($low+$high)/2); if($a[$mid-1]{'Time'}<$v) { $low=$mid+1; } if($v<=$a[$mid]{'Time'}) { $high=$mid-1; } } if($low==$high+2) { return $mid-1; } else { return 0; } }