#!/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;
$Value =~s/"/"/g;
$Value =~s/'/'/g;
$Value =~s/,/,/g;
if($Key eq 'Message')
{
$Value =~s/\x0D\x0A|\x0D|\x0A/
/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;
}
}