2 MesRegex = /^<div class="message"><div class="([a-z]+)(\d+)">/
3 ExtRegex = /^<div class="message"><!--([a-z]+)(\d+)-->/
5 attr_reader :name, :date, :vid, :state, :players, :rule
6 attr_reader :userid, :winner, :phase, :rooms, :voting
7 attr_reader :first_restart, :guard
9 attr_accessor :update_time
11 def initialize(name, vid, userid, update_time)
12 @name, @vid, @date, @state = name, vid, 0, 0
13 @userid, @update_time = userid, update_time
14 @first_restart = false
16 @players = Players.new
18 @period = S[:std_period] # minute
19 # night_period = @period / 2
26 addlog(announce(OPENING))
28 player = Player.new(1, MASTER, 0)
29 add_player(player, GERT_ENTRY)
33 return if @state >= State::Progress
35 if discussions(@date, @players.player(1)).size > S[:log_max]
37 change_state_sync(State::End)
40 @players.all.each {|p|
43 if (p.lastmsg_time && \
44 p.lastmsg_time < Time.now() - (S[:restart_minute] * 60))
49 change_state_sync(State::Welcome)
51 addlog(wsystem(c(AUTO_PARTING, pl_dels.join('、')))) if pl_dels != []
53 while @update_time < Time.now do
54 @update_time += S[:restart_minute] * 60
57 DB::Villages.restart(@vid, @update_time)
61 def add_player(player, message)
63 change_state_sync(State::Ready) if @players.size == @players.max
65 addlog(wsystem(c(JOINING, player.name)))
66 record("say", player, message)
69 def delete_player(player)
70 addlog(wsystem(c(PARTING, player.name)))
73 change_state_sync(State::Welcome)
76 def change_state_sync(st)
78 DB::Villages.change_state(@vid, st)
82 (@state < State::End && @update_time.to_i < Time.now.to_i)
86 if (@state == State::Welcome && !@players.adv_ready?)
87 if (@first_restart && @players.ready?)
99 (0..@date).collect {|i| datestr(i) }
102 def discussion_size(date) # use this, prologue only.
103 IO.readlines("db/vil/#{@vid}_#{date}.html").size
106 def discussions(date, player, reverse = false)
107 ary = IO.readlines("db/vil/#{@vid}_#{date}.html")
108 ary.reverse! if reverse
111 if (@state < State::Party && (line =~ MesRegex || line =~ ExtRegex))
112 next if (!player && $1 != 'say')
114 if (player && !player.admin?)
116 # night pid is room-number
118 when 'whisper' then next unless player.can_whisper
119 when 'god' then next unless (player.pid == pid || player.can_whisper)
120 when 'groan' then next unless player.dead?
121 when 'night' then next unless @rooms.in?(date, pid, player.pid)
122 when 'nextra' then next unless @rooms.in?(date, pid, player.pid)
131 !@voting.empty? && @phase != Phase::Apology
135 def record(type, player, msg, face = nil)
136 face = "face#{player.color}" if !face
137 player.cnt[@date] += 1 if (@phase == Phase::Sun && type == 'say')
138 player.lastmsg_time = Time.now() if @state < State::Progress
140 # PreVote || Vote || FinalVote || Room || AfterRoom || Morning
141 if (player.live? && (type == 'say' || type == 'night') && [1, 2, 4, 5, 6, 8].index(@phase))
142 raise ErrorMsg.new('フェイズと発言種類が合致しません')
145 type = 'night' if (@phase == Phase::Night && type == 'say')
146 type = 'groan' if (player.dead? && (type == 'say' || type =='night'))
147 type = 'say' if (player.live? && type == 'groan')
148 type = 'say' if (@state >= State::Party && type != 'say')
152 @rooms.last.each_with_index {|ary, i|
153 c = i if ary.index(player.pid)
156 typec = type + c.to_s
158 # don't insert \n in message, cause this line in convert to JSON.
160 %Q(<div class="#{typec}">) + \
161 %Q(<div class="#{type}">#{player.name}) + \
162 %Q(<table class="message_box"><tr>) + \
163 %Q(<td width="40"><img src="#{S[:image_dir]}#{face}.jpg"></td>) + \
164 %Q(<td width="16"><img src="#{S[:image_dir]}#{type}00.jpg"></td>) + \
165 %Q(<td><div class="mes_#{type}_body0">) + \
166 %Q(<div class="mes_#{type}_body1">#{msg}</div>) + \
168 %Q(</tr></table></div></div>\n)
170 raise ErrorMsg.new('夜ではありません') if (@phase != Phase::Night && type == 'night')
176 @_targets || @players.lives()
183 #######################################################
186 addlog(announce(SYSTEM_EXIT))
189 @players.all().each {|p|
190 alive = (p.dead == 0) ? '生存' : '死亡'
191 sk = (@date == 0) ? '役職決定前' : p.skill.name
193 a.push(c(TRUTH, p.name, p.userid, alive, sk))
196 addlog(announce(a.join('<br>')))
202 addlog(announce('終了しました'))
203 DB::Villages.finish(@vid, @players.keys.size)
204 DB::Users.registlast(@vid, @players.keys)
209 # Internal periodic work methods
210 #######################################################
213 @players.silents().each {|p|
214 p.sudden_death(@date)
215 addlog(announce(c(SUDDEN_DEATH, p.name)))
222 @players.votes.each {|ary|
224 s << [p.name, t.name].join(' » ')
225 votes[t] = votes[t].to_i + 1
226 max = votes[t] if (votes[t] > max)
230 result = votes.select {|k, v| v == max }
231 result.collect! {|ary| ary[0] }
232 votes.each_pair {|p, n| s << c(ANON_VOTING, n, p.name) }
233 addlog(announce(s.join('<br>')))
241 wcnt = @players.wolves().size
242 vcnt = @players.villagers().size
244 if (wcnt == 0 || vcnt == 0)
245 if (@players.select_skill(7).size > 0)
246 w = (wcnt == 0) ? YOKO_WIN_F : YOKO_WIN_W
251 addlog(announce(FOLK_WIN))
254 addlog(announce(WOLF_WIN))
257 elsif @date == @guard
259 addlog(announce(GUARD_WIN))
261 elsif @players.lives().size < 3
262 exe = @players.select_skill(Skill::Exorcist)
263 pas = @players.select_skill(Skill::Pastor)
265 if ((exe.empty? || exe.first.skill.pass == false) && \
266 ((pas.empty? || pas.first.skill.pass == false) || \
267 (pas.first.skill.pass && @date + 1 != @guard)))
270 addlog(announce(WOLF_WIN))
273 addlog(announce(FOLK_WIN))
279 change_state_sync(State::Party)
282 @players.all().each {|p|
283 alive = (p.dead == 0) ? '生存' : '死亡'
284 a.push(c(TRUTH, p.name, p.userid, alive, p.skill.name))
286 addlog(announce(a.join('<br>')))
288 @players.open_party()
292 def up_showlives(include_gert = true)
293 # Players@show_lives(), wtls
294 pls = @players.lives()
295 pls.delete(@players.player(1)) unless include_gert
296 s = c(LIVES, pls.collect {|p| p.name }.join('、'), pls.size)
300 def up_uptime(period = @period)
301 $logger.debug('from: ' + @update_time.inspect)
302 if @update_time > Time.now
303 @update_time = Time.now + (period * 60)
305 @update_time += period * 60
307 while (@update_time && @update_time.to_i < Time.now.to_i) do
308 @update_time += period * 60
310 $logger.debug('to: ' + @update_time.inspect)
315 #######################################################
317 def update(nocnt = nil)
318 return if (@state == State::End)
320 if (@state == State::Party || nocnt)
321 change_state_sync(State::End)
322 (nocnt) ? do_quit() : do_end()
327 (@date == 0) ? first_update() : _update()
346 (@prevote) ? start_room() : start_vote()
354 if picked.size == @players.lives().size
355 addlog(announce(STOP_EXECUTION))
358 @_targets = picked.dup
359 if ((@players.lives().size - picked.size) % 2) == 0
360 start_apology(picked)
375 when Phase::FinalVote
394 when Phase::AfterRoom
399 if @state == State::Party
401 elsif @players.lives().size > 2
415 @players.reset_count(@date)
417 @players.skill_mapping()
418 @guard = Regulation.guard(@players.size, @players.wolves().size)
419 @rule = Regulation.rule(@players.size)
420 change_state_sync(State::Progress)
422 addlog(announce(START))
424 record('say', @players.player(1), c(GERT_FIRST, @guard - @date, @guard - @date + 1))
425 @players.player(1).dead = 1 # die, not use Player#kill()
427 wolves = @players.wolves()
428 whisper = (wolves.size == 1) ? FIRST_WHISPER_LW : FIRST_WHISPER
429 wolves.each {|w| record('whisper', w, whisper) }
434 #######################################################
437 #mod 2008/11/08 tkt : for epilogue time change, yes, i know this code is not good...
438 #to-do attached plyaers.size and period time
439 #period = (@state == State::Party) ? S[:epilogue_period] : @period
441 if @state == State::Party
442 period = 1 * @players.size * (@date - 1)
443 $logger.debug('epilogue_period#{period} plaersize#{@players.size} date#{(@date - 1)}')
444 if period < S[:epilogue_period_min]
445 period = S[:epilogue_period_min]
446 elsif (@players.size >= 8 || (period > S[:epilogue_period]))
447 period = S[:epilogue_period]
449 #period = (@state == State::Party) ? period_epi : @period
457 unless @state == State::Party
458 limit = @guard - @date
459 addlog(wsystem(c(SUN_PHASE, limit)))
464 up_sudden_death() unless $DEBUG
465 addlog(wsystem(SUNSET_PHASE))
469 up_uptime(S[:vote_period])
470 @phase = Phase::PreVote
471 @voting = @players.lives().shuffle
472 addlog(wsystem(c(PREVOTE_PHASE, @voting.collect {|p| p.sname }.join(' » '))))
473 addlog(vote(c(ANN_VOTING, @voting.first.name)))
477 up_uptime(S[:vote_period])
479 res = (pl.prevote) ? '賛成' : '反対'
480 @players.vote_map([pl, res])
481 addlog(vote_res(c(VOTING, pl.name, res)))
482 addlog(vote(c(ANN_VOTING, @voting.first.name))) unless @voting.empty?
486 result = @players.collect_prevote()
488 @prevote = (vote['result']) ? false : true
491 @players.votes.each {|pl, |
492 t = (pl.prevote) ? '賛成した' : '反対した'
493 s << %Q(#{pl.name}は、投票に#{t}<br />)
495 s << "<br />賛成に#{vote['approve']}票、反対に#{vote['object']}票、"
496 t = (vote['result']) ? '処刑を実施することになった。' : '処刑は保留された。'
503 up_uptime(S[:vote_period])
506 @voting = @players.lives().shuffle
507 addlog(wsystem(c(VOTE_PHASE, @voting.collect {|p| p.sname }.join(' » '))))
508 addlog(vote(c(ANN_VOTING, @voting.first.name)))
512 up_uptime(S[:vote_period])
514 pl.vote_suffle(@players.lives()) unless pl.vote
515 target = @players.player(pl.vote)
516 @players.vote_map([pl, target])
517 addlog(vote_res(c(VOTING, pl.name, target.name)))
518 addlog(vote(c(ANN_VOTING, @voting.first.name))) unless @voting.empty?
525 addlog(announce(c(EXECUTION, picked.first.name)))
526 picked.first.execute(@date)
532 def start_apology(targets)
533 up_uptime(@period / 3.0)
534 @phase = Phase::Apology
535 @voting = @_targets.shuffle
536 x = targets.collect {|t| t.name}.join('と、')
537 addlog(wsystem(c(APOLOGY_PHASE, x, @voting.collect {|p| p.sname }.join(' » '))))
538 addlog(apology(c(APOLOGY_START, @voting.first.name)))
542 up_uptime(@period / 3.0)
544 addlog(apology(c(APOLOGY_START, @voting.first.name))) unless @voting.empty?
551 up_uptime(S[:vote_period])
553 @phase = Phase::FinalVote
554 @voting = @players.lives().shuffle - @_targets
555 x = @_targets.collect {|t| t.name}.join('と、')
556 addlog(wsystem(c(FINALVOTE_PHASE, x, @voting.collect {|p| p.sname }.join(' » '))))
557 addlog(vote(c(ANN_VOTING, @voting.first.name)))
561 up_uptime(S[:vote_period])
563 pl.vote_suffle(vtargets()) unless pl.vote
564 target = @players.player(pl.vote)
565 @players.vote_map([pl, target])
566 addlog(vote_res(c(VOTING, pl.name, target.name)))
567 addlog(vote(c(ANN_VOTING, @voting.first.name))) unless @voting.empty?
574 addlog(announce(c(EXECUTION, picked.first.name)))
575 picked.first.execute(@date)
577 addlog(announce(STOP_EXECUTION))
586 @voting = @players.lives().shuffle
587 addlog(wsystem(c(ROOM_PHASE, @voting.collect {|p| p.sname }.join(' » '))))
593 up_uptime(S[:vote_period])
594 addlog(room(c(ANN_ROOM_VOTING, @voting.first.name)))
600 if (!pl.vote || pl.vote == pl.pid)
601 targets = @voting.select {|t| !pl.yesterday_mate.index(t.pid) }
602 pl.vote_suffle(targets)
604 mate = @players.player(pl.vote)
605 addlog(room_res(c(ROOM_VOTING, pl.name, mate.name)))
608 #if (@rule == Rule::Standard || @date != 1) mod 2008/11/08 tkt:change rule for 3 members room
609 if (@date != 1 || (@rule == Rule::Standard && (@players.size % 2 == 0)))
610 @players.room_map([pl, mate])
612 last_room = @players.mates.last
613 if (last_room && last_room.size == 2)
616 names = lr.map {|pl| pl.name }
617 addlog(room_res2(c(ROOM_RESULT_ADV, names.join('と、'))))
618 @players.room_remap(lr)
619 # last room mate added, go next
621 @players.room_map([pl, mate])
622 @voting.unshift(mate) unless @voting.size < 3
623 # not filled in the room
627 unless (@voting.empty? || @voting.size <= 3)
628 up_uptime(S[:vote_period])
629 addlog(room(c(ANN_ROOM_VOTING, @voting.first.name)))
631 @players.vote_suffle(@voting) unless @voting.empty?
635 @players.room_map(@voting.dup)
636 x = @voting.collect {|p| p.name}.join('と、')
637 addlog(room_res(c(BUSH_ROOM, x)))
657 result = @players.room_mapping()
658 result.each {|room_mates| @rooms.last << room_mates.collect {|x| x.pid } }
662 result.each {|room_mates|
663 s << room_mates.collect {|x| x.name }.join(' » ') + '<br />'
665 s << last.collect {|x| x.name }.join('、') + '<br />'
671 up_uptime(S[:vote_period])
672 @phase = Phase::AfterRoom
673 addlog(wsystem(AFTERROOM_PHASE))
681 @phase = Phase::Night
682 addlog(wsystem(NIGHT_PHASE))
686 @players.end_action(self)
690 @players.reset_count(@date)
691 @prevote = false if (@prevote && @players.deads().size > 1)
694 yesterday_room = @rooms[@date - 2].dup
695 last = yesterday_room.pop
696 yesterday_room.each {|room_mates|
697 s << room_mates.collect {|x| @players.player(x).name }.join(' » ') + '<br />'
699 s << last.collect {|x| @players.player(x).name }.join('、') + '<br />'
703 deads = @players.killed(@date-1)
705 addlog(announce(KILLMISS))
707 _ = deads.collect {|pl| pl.name }.join('と、')
708 addlog(announce(c(KILLED, _)))
713 return if @state == 3
719 up_uptime(S[:vote_period] + 0.2)
720 @phase = Phase::Morning
721 addlog(wsystem(MORNING_PHASE))
729 #######################################################
732 File.open("db/vil/#{@vid}_#{@date}.html", 'a') {|of|
733 of.flock(File::LOCK_EX)
735 of.flock(File::LOCK_UN)
738 public :addlog # FIXME! ==> sysrecord
741 $logger.debug('restructuring log...')
742 s = File.read("db/vil/#{@vid}_#{@date}.html")
745 File.open("db/vil/#{@vid}_#{@date}.html", 'w') {|of|
746 of.flock(File::LOCK_EX)
748 of.flock(File::LOCK_UN)
754 ary = str.split("\n")
759 break if line =~ /^<div class="message"><div class="system">#{NIGHT_PHASE}/o
762 night_hash = _logremap_night(ary)
763 keys = night_hash.keys
765 keys.sort.each {|key|
766 members = @rooms.members(@date, key).collect {|i| @players.player(i).name }
767 _ << wsystem(c(ROOM_HEAD, members.join('、'))) + "\n"
768 _ << night_hash[key].join("\n") + "\n"
770 _ << wsystem(GROAN_HEAD) + "\n"
771 _ << night_hash['groan'].join("\n") + "\n"
775 def _logremap_night(night_ary)
777 night_ary.each {|line|
778 if (line =~ MesRegex || line =~ ExtRegex)
781 when /(night)|(nextra)/
782 h[pid] = [] unless h.has_key?(pid)
784 when /(whisper)|(god)/
785 num = @rooms.number(@date, pid)
786 h[num] = [] unless h.has_key?(num)
791 $logger.debug('state lock bug?')
793 # raise 'Must not happen.'
796 $logger.debug('multiple logremap bug?')
798 # raise 'Must not happen.'
807 elsif (@state == State::End && date + 1 >= @date)
808 (date == @date) ? '終了' : 'エピローグ'
809 elsif (@state == State::Party && date == @date)