OSDN Git Service

43f532aaf7a00700b8a49728bc6a029028ea10b4
[jindolf/Jindolf.git] / src / main / java / jp / sourceforge / jindolf / Period.java
1 /*\r
2  * daily period in village\r
3  *\r
4  * Copyright(c) 2008 olyutorskii\r
5  * $Id: Period.java 1028 2010-05-13 10:15:11Z olyutorskii $\r
6  */\r
7 \r
8 package jp.sourceforge.jindolf;\r
9 \r
10 import java.io.IOException;\r
11 import java.util.Collections;\r
12 import java.util.HashMap;\r
13 import java.util.HashSet;\r
14 import java.util.LinkedList;\r
15 import java.util.List;\r
16 import java.util.Map;\r
17 import java.util.Set;\r
18 import jp.sourceforge.jindolf.corelib.EventFamily;\r
19 import jp.sourceforge.jindolf.corelib.GameRole;\r
20 import jp.sourceforge.jindolf.corelib.LandDef;\r
21 import jp.sourceforge.jindolf.corelib.PeriodType;\r
22 import jp.sourceforge.jindolf.corelib.SysEventType;\r
23 import jp.sourceforge.jindolf.corelib.TalkType;\r
24 import jp.sourceforge.jindolf.corelib.Team;\r
25 import jp.sourceforge.jindolf.corelib.VillageState;\r
26 import jp.sourceforge.jindolf.parser.DecodedContent;\r
27 import jp.sourceforge.jindolf.parser.EntityConverter;\r
28 import jp.sourceforge.jindolf.parser.HtmlAdapter;\r
29 import jp.sourceforge.jindolf.parser.HtmlParseException;\r
30 import jp.sourceforge.jindolf.parser.HtmlParser;\r
31 import jp.sourceforge.jindolf.parser.PageType;\r
32 import jp.sourceforge.jindolf.parser.SeqRange;\r
33 \r
34 /**\r
35  * いわゆる「日」。\r
36  * 村の進行の一区切り。プロローグやエピローグも含まれる。\r
37  *\r
38  * 将来、24時間更新でなくなる可能性の考慮が必要。\r
39  * 人気のないプロローグなどで、\r
40  * 24時間以上の期間を持つPeriodが生成される可能性の考慮が必要。\r
41  */\r
42 public class Period{\r
43     // TODO Comparable も implement する?\r
44 \r
45     private static final HtmlParser PARSER = new HtmlParser();\r
46     private static final PeriodHandler HANDLER =\r
47             new PeriodHandler();\r
48 \r
49     static{\r
50         PARSER.setBasicHandler   (HANDLER);\r
51         PARSER.setSysEventHandler(HANDLER);\r
52         PARSER.setTalkHandler    (HANDLER);\r
53     }\r
54 \r
55     /**\r
56      * Periodを更新する。Topicのリストが更新される。\r
57      * @param period 日\r
58      * @param force trueなら強制再読み込み。\r
59      * falseならまだ読み込んで無い時のみ読み込み。\r
60      * @throws IOException ネットワーク入力エラー\r
61      */\r
62     public static void parsePeriod(Period period, boolean force)\r
63             throws IOException{\r
64         if( ! force && period.hasLoaded() ) return;\r
65 \r
66         Village village = period.getVillage();\r
67         Land land = village.getParentLand();\r
68         ServerAccess server = land.getServerAccess();\r
69 \r
70         if(village.getState() != VillageState.PROGRESS){\r
71             period.isFullOpen = true;\r
72         }else if(period.getType() != PeriodType.PROGRESS){\r
73             period.isFullOpen = true;\r
74         }else{\r
75             period.isFullOpen = false;\r
76         }\r
77 \r
78         HtmlSequence html = server.getHTMLPeriod(period);\r
79 \r
80         period.topicList.clear();\r
81 \r
82         boolean wasHot = period.isHot();\r
83 \r
84         HANDLER.setPeriod(period);\r
85         DecodedContent content = html.getContent();\r
86         try{\r
87             PARSER.parseAutomatic(content);\r
88         }catch(HtmlParseException e){\r
89             Jindolf.logger().warn("発言抽出に失敗", e);\r
90         }\r
91 \r
92         if(wasHot && ! period.isHot() ){\r
93             parsePeriod(period, true);\r
94             return;\r
95         }\r
96 \r
97         return;\r
98     }\r
99 \r
100     private final Village homeVillage;\r
101     private final PeriodType periodType;\r
102     private final int day;\r
103     private int limitHour;\r
104     private int limitMinute;\r
105     // TODO 更新月日も入れるべきか。\r
106     private String loginName;\r
107     private boolean isFullOpen = false;\r
108 \r
109     private final List<Topic> topicList = new LinkedList<Topic>();\r
110     private final List<Topic> unmodList =\r
111             Collections.unmodifiableList(this.topicList);\r
112 \r
113     /**\r
114      * この Period が進行中の村の最新日で、\r
115      * 今まさに次々と発言が蓄積されているときは\r
116      * true になる。\r
117      * ※重要: Hot な Period は meslog クエリーを使ってダウンロードできない。\r
118      */\r
119     private boolean isHot;\r
120 \r
121     /**\r
122      * Periodを生成する。\r
123      * この段階では発言データのロードは行われない。\r
124      * デフォルトで非Hot状態。\r
125      * @param homeVillage 所属するVillage\r
126      * @param periodType Period種別\r
127      * @param day Period通番\r
128      * @throws java.lang.NullPointerException 引数にnullが渡された場合。\r
129      */\r
130     public Period(Village homeVillage,\r
131                    PeriodType periodType,\r
132                    int day)\r
133                    throws NullPointerException{\r
134         this(homeVillage, periodType, day, false);\r
135         return;\r
136     }\r
137 \r
138     /**\r
139      * Periodを生成する。\r
140      * この段階では発言データのロードは行われない。\r
141      * @param homeVillage 所属するVillage\r
142      * @param periodType Period種別\r
143      * @param day Period通番\r
144      * @param isHot Hotか否か\r
145      * @throws java.lang.NullPointerException 引数にnullが渡された場合。\r
146      */\r
147     private Period(Village homeVillage,\r
148                     PeriodType periodType,\r
149                     int day,\r
150                     boolean isHot)\r
151                     throws NullPointerException{\r
152         if(   homeVillage == null\r
153            || periodType  == null ) throw new NullPointerException();\r
154         if(day < 0){\r
155             throw new IllegalArgumentException("Period day is too small !");\r
156         }\r
157         switch(periodType){\r
158         case PROLOGUE:\r
159             assert day == 0;\r
160             break;\r
161         case PROGRESS:\r
162         case EPILOGUE:\r
163             assert day > 0;\r
164             break;\r
165         default:\r
166             assert false;\r
167             break;\r
168         }\r
169 \r
170         this.homeVillage = homeVillage;\r
171         this.periodType  = periodType;\r
172         this.day         = day;\r
173 \r
174         unload();\r
175 \r
176         this.isHot = isHot;\r
177 \r
178         return;\r
179     }\r
180 \r
181     /**\r
182      * 所属する村を返す。\r
183      * @return 村\r
184      */\r
185     public Village getVillage(){\r
186         return this.homeVillage;\r
187     }\r
188 \r
189     /**\r
190      * Period種別を返す。\r
191      * @return 種別\r
192      */\r
193     public PeriodType getType(){\r
194         return this.periodType;\r
195     }\r
196 \r
197     /**\r
198      * Period通番を返す。\r
199      * プロローグは常に0番。\r
200      * n日目のゲーム進行日はn番\r
201      * エピローグは最後のゲーム進行日+1番\r
202      * @return Period通番\r
203      */\r
204     public int getDay(){\r
205         return this.day;\r
206     }\r
207 \r
208     /**\r
209      * 更新時刻の文字表記を返す。\r
210      * @return 更新時刻の文字表記\r
211      */\r
212     public String getLimit(){\r
213         StringBuilder result = new StringBuilder();\r
214 \r
215         if(this.limitHour < 10) result.append('0');\r
216         result.append(this.limitHour).append(':');\r
217 \r
218         if(this.limitMinute < 10) result.append('0');\r
219         result.append(this.limitMinute);\r
220 \r
221         return result.toString();\r
222     }\r
223 \r
224     /**\r
225      * Hotか否か返す。\r
226      * @return Hotか否か\r
227      */\r
228     public boolean isHot(){\r
229         return this.isHot;\r
230     }\r
231 \r
232     /**\r
233      * Hotか否か設定する。\r
234      * @param isHotArg Hot指定\r
235      */\r
236     public void setHot(boolean isHotArg){\r
237         this.isHot = isHotArg;\r
238     }\r
239 \r
240     /**\r
241      * プロローグか否か判定する。\r
242      * @return プロローグならtrue\r
243      */\r
244     public boolean isPrologue(){\r
245         if(getType() == PeriodType.PROLOGUE) return true;\r
246         return false;\r
247     }\r
248 \r
249     /**\r
250      * エピローグか否か判定する。\r
251      * @return エピローグならtrue\r
252      */\r
253     public boolean isEpilogue(){\r
254         if(getType() == PeriodType.EPILOGUE) return true;\r
255         return false;\r
256     }\r
257 \r
258     /**\r
259      * 進行日か否か判定する。\r
260      * @return 進行日ならtrue\r
261      */\r
262     public boolean isProgress(){\r
263         if(getType() == PeriodType.PROGRESS) return true;\r
264         return false;\r
265     }\r
266 \r
267     /**\r
268      * このPeriodにアクセスするためのクエリーを生成する。\r
269      * @return CGIに渡すクエリー\r
270      */\r
271     public String getCGIQuery(){\r
272         StringBuilder result = new StringBuilder();\r
273 \r
274         Village village = getVillage();\r
275         result.append(village.getCGIQuery());\r
276 \r
277         if(isHot()){\r
278             result.append("&mes=all");   // 全表示指定\r
279             return result.toString();\r
280         }\r
281 \r
282         Land land = village.getParentLand();\r
283         LandDef ldef = land.getLandDef();\r
284 \r
285         if(ldef.getLandId().equals("wolfg")){\r
286             result.append("&meslog=");\r
287             String dnum = "000" + (getDay() - 1);\r
288             dnum = dnum.substring(dnum.length() - 3);\r
289             switch(getType()){\r
290             case PROLOGUE:\r
291                 result.append("000_ready");\r
292                 break;\r
293             case PROGRESS:\r
294                 result.append(dnum).append("_progress");\r
295                 break;\r
296             case EPILOGUE:\r
297                 result.append(dnum).append("_party");\r
298                 break;\r
299             default:\r
300                 assert false;\r
301                 return null;\r
302             }\r
303         }else{\r
304             result.append("&meslog=").append(village.getVillageID());\r
305             switch(getType()){\r
306             case PROLOGUE:\r
307                 result.append("_ready_0");\r
308                 break;\r
309             case PROGRESS:\r
310                 result.append("_progress_").append(getDay() - 1);\r
311                 break;\r
312             case EPILOGUE:\r
313                 result.append("_party_").append(getDay() - 1);\r
314                 break;\r
315             default:\r
316                 assert false;\r
317                 return null;\r
318             }\r
319         }\r
320 \r
321 \r
322         result.append("&mes=all");\r
323 \r
324         return result.toString();\r
325     }\r
326 \r
327     /**\r
328      * Periodに含まれるTopicのリストを返す。\r
329      * このリストは上書き操作不能。\r
330      * @return Topicのリスト\r
331      */\r
332     public List<Topic> getTopicList(){\r
333         return this.unmodList;\r
334     }\r
335 \r
336     /**\r
337      * Periodに含まれるTopicの総数を返す。\r
338      * @return Topic総数\r
339      */\r
340     public int getTopics(){\r
341         return this.topicList.size();\r
342     }\r
343 \r
344     /**\r
345      * Topicを追加する。\r
346      * @param topic Topic\r
347      * @throws java.lang.NullPointerException nullが渡された場合。\r
348      */\r
349     protected void addTopic(Topic topic) throws NullPointerException{\r
350         if(topic == null) throw new NullPointerException();\r
351         this.topicList.add(topic);\r
352         return;\r
353     }\r
354 \r
355     /**\r
356      * Periodのキャプション文字列を返す。\r
357      * 主な用途はタブ画面の耳のラベルなど。\r
358      * @return キャプション文字列\r
359      */\r
360     public String getCaption(){\r
361         String result;\r
362 \r
363         switch(getType()){\r
364         case PROLOGUE:\r
365             result = "プロローグ";\r
366             break;\r
367         case PROGRESS:\r
368             result = getDay() + "日目";\r
369             break;\r
370         case EPILOGUE:\r
371             result = "エピローグ";\r
372             break;\r
373         default:\r
374             assert false;\r
375             result = null;\r
376             break;\r
377         }\r
378 \r
379         return result;\r
380     }\r
381 \r
382     /**\r
383      * このPeriodをダウンロードしたときのログイン名を返す。\r
384      * @return ログイン名。ログアウト中はnull。\r
385      */\r
386     public String getLoginName(){\r
387         return this.loginName;\r
388     }\r
389 \r
390     /**\r
391      * 公開発言番号にマッチする発言を返す。\r
392      * @param talkNo 公開発言番号\r
393      * @return 発言。見つからなければnull\r
394      */\r
395     public Talk getNumberedTalk(int talkNo){\r
396         if(talkNo <= 0) throw new IllegalArgumentException();\r
397 \r
398         for(Topic topic : this.topicList){\r
399             if( ! (topic instanceof Talk) ) continue;\r
400             Talk talk = (Talk) topic;\r
401             if(talkNo == talk.getTalkNo()) return talk;\r
402         }\r
403 \r
404         return null;\r
405     }\r
406 \r
407     /**\r
408      * このPeriodの内容にゲーム進行上隠された部分がある可能性を判定する。\r
409      * @return 隠れた要素がありうるならfalse\r
410      */\r
411     public boolean isFullOpen(){\r
412         return this.isFullOpen;\r
413     }\r
414 \r
415     /**\r
416      * ロード済みか否かチェックする。\r
417      * @return ロード済みならtrue\r
418      */\r
419     public boolean hasLoaded(){\r
420         return getTopics() > 0;\r
421     }\r
422 \r
423     /**\r
424      * 発言データをアンロードする。\r
425      */\r
426     public void unload(){\r
427         this.limitHour = 0;\r
428         this.limitMinute = 0;\r
429         this.loginName = null;\r
430         this.isFullOpen = false;\r
431 \r
432         this.isHot = false;\r
433 \r
434         this.topicList.clear();\r
435 \r
436         return;\r
437     }\r
438 \r
439     /**\r
440      * 襲撃メッセージの有無を判定する。\r
441      * 決着が付くまで非狼陣営には見えない。\r
442      * 偽装GJでは狼にも見えない。\r
443      * @return 襲撃メッセージがあればtrue\r
444      */\r
445     public boolean hasAssaultTried(){\r
446         for(Topic topic : this.topicList){\r
447             if(topic instanceof Talk){\r
448                 Talk talk = (Talk) topic;\r
449                 if(talk.getTalkCount() <= 0) return true;\r
450             }else if(topic instanceof SysEvent){\r
451                 SysEvent sysEvent = (SysEvent) topic;\r
452                 SysEventType type = sysEvent.getSysEventType();\r
453                 if(type == SysEventType.ASSAULT) return true;\r
454             }\r
455         }\r
456 \r
457         return false;\r
458     }\r
459 \r
460     /**\r
461      * 処刑されたAvatarを返す。\r
462      * @return 処刑されたAvatar。突然死などなんらかの理由でいない場合はnull\r
463      */\r
464     public Avatar getExecutedAvatar(){\r
465         Avatar result = null;\r
466 \r
467         for(Topic topic : getTopicList()){\r
468             if( ! (topic instanceof SysEvent) ) continue;\r
469             SysEvent event = (SysEvent) topic;\r
470             result = event.getExecutedAvatar();\r
471             if(result != null) break;\r
472         }\r
473 \r
474         return result;\r
475     }\r
476 \r
477     /**\r
478      * 投票に参加したAvatarの集合を返す。\r
479      * @return 投票に参加したAvatarのSet\r
480      */\r
481     public Set<Avatar> getVoterSet(){\r
482         Set<Avatar> result = new HashSet<Avatar>();\r
483 \r
484         for(Topic topic : getTopicList()){\r
485             if( ! (topic instanceof SysEvent) ) continue;\r
486             SysEvent event = (SysEvent) topic;\r
487             result = event.getVoterSet(result);\r
488         }\r
489 \r
490         return result;\r
491     }\r
492 \r
493     /**\r
494      * 任意のタイプのシステムイベントを返す。\r
495      * 複数存在する場合、返すのは最初の一つだけ。\r
496      * @param type イベントタイプ\r
497      * @return システムイベント\r
498      */\r
499     public SysEvent getTypedSysEvent(SysEventType type){\r
500         for(Topic topic : getTopicList()){\r
501             if( ! (topic instanceof SysEvent) ) continue;\r
502             SysEvent event = (SysEvent) topic;\r
503             if(event.getSysEventType() == type) return event;\r
504         }\r
505 \r
506         return null;\r
507     }\r
508 \r
509     /**\r
510      * Periodパース用ハンドラ。\r
511      */\r
512     private static class PeriodHandler extends HtmlAdapter{\r
513 \r
514         private static final int TALKTYPE_NUM = TalkType.values().length;\r
515 \r
516         private final EntityConverter converter =\r
517                 new EntityConverter();\r
518 \r
519         private final Map<Avatar, int[]> countMap =\r
520                 new HashMap<Avatar, int[]>();\r
521 \r
522         private Period period = null;\r
523 \r
524         private TalkType talkType;\r
525         private Avatar avatar;\r
526         private int talkNo;\r
527         private String anchorId;\r
528         private int talkHour;\r
529         private int talkMinute;\r
530         private DecodedContent talkContent = null;\r
531 \r
532         private EventFamily eventFamily;\r
533         private SysEventType sysEventType;\r
534         private DecodedContent eventContent = null;\r
535         private final List<Avatar> avatarList = new LinkedList<Avatar>();\r
536         private final List<GameRole> roleList = new LinkedList<GameRole>();\r
537         private final List<Integer> integerList = new LinkedList<Integer>();\r
538         private final List<CharSequence>  charseqList =\r
539             new LinkedList<CharSequence>();\r
540 \r
541         /**\r
542          * コンストラクタ。\r
543          */\r
544         public PeriodHandler(){\r
545             super();\r
546             return;\r
547         }\r
548 \r
549         /**\r
550          * パース結果を格納するPeriodを設定する。\r
551          * @param period Period\r
552          */\r
553         public void setPeriod(Period period){\r
554             this.period = period;\r
555             return;\r
556         }\r
557 \r
558         /**\r
559          * 文字列断片からAvatarを得る。\r
560          * 村に未登録のAvatarであればついでに登録される。\r
561          * @param content 文字列\r
562          * @param range 文字列内のAvatarフルネームを示す領域\r
563          * @return Avatar\r
564          */\r
565         private Avatar toAvatar(DecodedContent content, SeqRange range){\r
566             Village village = this.period.getVillage();\r
567             String fullName = this.converter\r
568                                   .convert(content, range)\r
569                                   .toString();\r
570             Avatar result = village.getAvatar(fullName);\r
571             if(result == null){\r
572                 result = new Avatar(fullName);\r
573                 village.addAvatar(result);\r
574             }\r
575 \r
576             return result;\r
577         }\r
578 \r
579         /**\r
580          * Avatar別、会話種ごとに発言回数をカウントする。\r
581          * 1から始まる。\r
582          * @param targetAvatar 対象Avatar\r
583          * @param targetType 対象会話種\r
584          * @return カウント数\r
585          */\r
586         private int countUp(Avatar targetAvatar, TalkType targetType){\r
587             int[] countArray = this.countMap.get(targetAvatar);\r
588             if(countArray == null){\r
589                 countArray = new int[TALKTYPE_NUM];\r
590                 this.countMap.put(targetAvatar, countArray);\r
591             }\r
592             int count = ++countArray[targetType.ordinal()];\r
593             return count;\r
594         }\r
595 \r
596         /**\r
597          * {@inheritDoc}\r
598          * @param content {@inheritDoc}\r
599          * @throws HtmlParseException {@inheritDoc}\r
600          */\r
601         @Override\r
602         public void startParse(DecodedContent content)\r
603                 throws HtmlParseException{\r
604             this.period.loginName = null;\r
605             this.period.topicList.clear();\r
606             this.countMap.clear();\r
607             return;\r
608         }\r
609 \r
610         /**\r
611          * {@inheritDoc}\r
612          * @param content {@inheritDoc}\r
613          * @param loginRange {@inheritDoc}\r
614          * @throws HtmlParseException {@inheritDoc}\r
615          */\r
616         @Override\r
617         public void loginName(DecodedContent content, SeqRange loginRange)\r
618                 throws HtmlParseException{\r
619             DecodedContent loginName =\r
620                     this.converter.convert(content, loginRange);\r
621 \r
622             this.period.loginName = loginName.toString();\r
623 \r
624             return;\r
625         }\r
626 \r
627         /**\r
628          * {@inheritDoc}\r
629          * @param type {@inheritDoc}\r
630          * @throws HtmlParseException {@inheritDoc}\r
631          */\r
632         @Override\r
633         public void pageType(PageType type) throws HtmlParseException{\r
634             if(type != PageType.PERIOD_PAGE){\r
635                 throw new HtmlParseException(\r
636                         "意図しないページを読み込もうとしました。");\r
637             }\r
638             return;\r
639         }\r
640 \r
641         /**\r
642          * {@inheritDoc}\r
643          * @param month {@inheritDoc}\r
644          * @param day {@inheritDoc}\r
645          * @param hour {@inheritDoc}\r
646          * @param minute {@inheritDoc}\r
647          * @throws HtmlParseException {@inheritDoc}\r
648          */\r
649         @Override\r
650         public void commitTime(int month, int day, int hour, int minute)\r
651                 throws HtmlParseException{\r
652             this.period.limitHour   = hour;\r
653             this.period.limitMinute = minute;\r
654             return;\r
655         }\r
656 \r
657         /**\r
658          * {@inheritDoc}\r
659          * 自分へのリンクが無いかチェックする。\r
660          * 自分へのリンクが見つかればこのPeriodを非Hotにする。\r
661          * 自分へのリンクがあるということは、\r
662          * 今読んでるHTMLは別のPeriodのために書かれたものということ。\r
663          * 考えられる原因は、HotだったPeriodがゲーム進行に従い\r
664          * Hotでなくなったこと。\r
665          * @param content {@inheritDoc}\r
666          * @param anchorRange {@inheritDoc}\r
667          * @param periodType {@inheritDoc}\r
668          * @param day {@inheritDoc}\r
669          * @throws HtmlParseException {@inheritDoc}\r
670          */\r
671         @Override\r
672         public void periodLink(DecodedContent content,\r
673                                 SeqRange anchorRange,\r
674                                 PeriodType periodType,\r
675                                 int day)\r
676                 throws HtmlParseException{\r
677 \r
678             if(this.period.getType() != periodType) return;\r
679 \r
680             if(   periodType == PeriodType.PROGRESS\r
681                && this.period.getDay() != day ){\r
682                 return;\r
683             }\r
684 \r
685             if( ! anchorRange.isValid() ) return;\r
686 \r
687             this.period.setHot(false);\r
688 \r
689             return;\r
690         }\r
691 \r
692         /**\r
693          * {@inheritDoc}\r
694          * @throws HtmlParseException {@inheritDoc}\r
695          */\r
696         @Override\r
697         public void startTalk() throws HtmlParseException{\r
698             this.talkType = null;\r
699             this.avatar = null;\r
700             this.talkNo = -1;\r
701             this.anchorId = null;\r
702             this.talkHour = -1;\r
703             this.talkMinute = -1;\r
704             this.talkContent = new DecodedContent(100 + 1);\r
705 \r
706             return;\r
707         }\r
708 \r
709         /**\r
710          * {@inheritDoc}\r
711          * @param type {@inheritDoc}\r
712          * @throws HtmlParseException {@inheritDoc}\r
713          */\r
714         @Override\r
715         public void talkType(TalkType type)\r
716                 throws HtmlParseException{\r
717             this.talkType = type;\r
718             return;\r
719         }\r
720 \r
721         /**\r
722          * {@inheritDoc}\r
723          * @param content {@inheritDoc}\r
724          * @param avatarRange {@inheritDoc}\r
725          * @throws HtmlParseException {@inheritDoc}\r
726          */\r
727         @Override\r
728         public void talkAvatar(DecodedContent content, SeqRange avatarRange)\r
729                 throws HtmlParseException{\r
730             this.avatar = toAvatar(content, avatarRange);\r
731             return;\r
732         }\r
733 \r
734         /**\r
735          * {@inheritDoc}\r
736          * @param hour {@inheritDoc}\r
737          * @param minute {@inheritDoc}\r
738          * @throws HtmlParseException {@inheritDoc}\r
739          */\r
740         @Override\r
741         public void talkTime(int hour, int minute)\r
742                 throws HtmlParseException{\r
743             this.talkHour = hour;\r
744             this.talkMinute = minute;\r
745             return;\r
746         }\r
747 \r
748         /**\r
749          * {@inheritDoc}\r
750          * @param tno {@inheritDoc}\r
751          * @throws HtmlParseException {@inheritDoc}\r
752          */\r
753         @Override\r
754         public void talkNo(int tno) throws HtmlParseException{\r
755             this.talkNo = tno;\r
756             return;\r
757         }\r
758 \r
759         /**\r
760          * {@inheritDoc}\r
761          * @param content {@inheritDoc}\r
762          * @param idRange {@inheritDoc}\r
763          * @throws HtmlParseException {@inheritDoc}\r
764          */\r
765         @Override\r
766         public void talkId(DecodedContent content, SeqRange idRange)\r
767                 throws HtmlParseException{\r
768             this.anchorId = content.subSequence(idRange.getStartPos(),\r
769                                                 idRange.getEndPos()   )\r
770                                    .toString();\r
771             return;\r
772         }\r
773 \r
774         /**\r
775          * {@inheritDoc}\r
776          * @param content {@inheritDoc}\r
777          * @param textRange {@inheritDoc}\r
778          * @throws HtmlParseException {@inheritDoc}\r
779          */\r
780         @Override\r
781         public void talkText(DecodedContent content, SeqRange textRange)\r
782                 throws HtmlParseException{\r
783             this.converter.append(this.talkContent, content, textRange);\r
784             return;\r
785         }\r
786 \r
787         /**\r
788          * {@inheritDoc}\r
789          * @throws HtmlParseException {@inheritDoc}\r
790          */\r
791         @Override\r
792         public void talkBreak()\r
793                 throws HtmlParseException{\r
794             this.talkContent.append('\n');\r
795             return;\r
796         }\r
797 \r
798         /**\r
799          * {@inheritDoc}\r
800          * @throws HtmlParseException {@inheritDoc}\r
801          */\r
802         @Override\r
803         public void endTalk() throws HtmlParseException{\r
804             Talk talk = new Talk(this.period,\r
805                                  this.talkType,\r
806                                  this.avatar,\r
807                                  this.talkNo,\r
808                                  this.anchorId,\r
809                                  this.talkHour, this.talkMinute,\r
810                                  this.talkContent );\r
811 \r
812             int count = countUp(this.avatar, this.talkType);\r
813             talk.setCount(count);\r
814 \r
815             this.period.addTopic(talk);\r
816 \r
817             this.talkType = null;\r
818             this.avatar = null;\r
819             this.talkNo = -1;\r
820             this.anchorId = null;\r
821             this.talkHour = -1;\r
822             this.talkMinute = -1;\r
823             this.talkContent = null;\r
824 \r
825             return;\r
826         }\r
827 \r
828         /**\r
829          * {@inheritDoc}\r
830          * @param family {@inheritDoc}\r
831          * @throws HtmlParseException {@inheritDoc}\r
832          */\r
833         @Override\r
834         public void startSysEvent(EventFamily family)\r
835                 throws HtmlParseException{\r
836             this.eventFamily = family;\r
837             this.sysEventType = null;\r
838             this.eventContent = new DecodedContent();\r
839             this.avatarList.clear();\r
840             this.roleList.clear();\r
841             this.integerList.clear();\r
842             this.charseqList.clear();\r
843             return;\r
844         }\r
845 \r
846         /**\r
847          * {@inheritDoc}\r
848          * @param type {@inheritDoc}\r
849          * @throws HtmlParseException {@inheritDoc}\r
850          */\r
851         @Override\r
852         public void sysEventType(SysEventType type)\r
853                 throws HtmlParseException{\r
854             this.sysEventType = type;\r
855             return;\r
856         }\r
857 \r
858         /**\r
859          * {@inheritDoc}\r
860          * @param content {@inheritDoc}\r
861          * @param contentRange {@inheritDoc}\r
862          * @throws HtmlParseException {@inheritDoc}\r
863          */\r
864         @Override\r
865         public void sysEventContent(DecodedContent content,\r
866                                       SeqRange contentRange)\r
867                 throws HtmlParseException{\r
868             this.converter.append(this.eventContent, content, contentRange);\r
869             return;\r
870         }\r
871 \r
872         /**\r
873          * {@inheritDoc}\r
874          * @param content {@inheritDoc}\r
875          * @param anchorRange {@inheritDoc}\r
876          * @param contentRange {@inheritDoc}\r
877          * @throws HtmlParseException {@inheritDoc}\r
878          */\r
879         @Override\r
880         public void sysEventContentAnchor(DecodedContent content,\r
881                                              SeqRange anchorRange,\r
882                                              SeqRange contentRange)\r
883                 throws HtmlParseException{\r
884             this.converter.append(this.eventContent, content, contentRange);\r
885             return;\r
886         }\r
887 \r
888         /**\r
889          * {@inheritDoc}\r
890          * @throws HtmlParseException {@inheritDoc}\r
891          */\r
892         @Override\r
893         public void sysEventContentBreak() throws HtmlParseException{\r
894             this.eventContent.append('\n');\r
895             return;\r
896         }\r
897 \r
898         /**\r
899          * {@inheritDoc}\r
900          * @param content {@inheritDoc}\r
901          * @param entryNo {@inheritDoc}\r
902          * @param avatarRange {@inheritDoc}\r
903          * @throws HtmlParseException {@inheritDoc}\r
904          */\r
905         @Override\r
906         public void sysEventOnStage(DecodedContent content,\r
907                                       int entryNo,\r
908                                       SeqRange avatarRange)\r
909                 throws HtmlParseException{\r
910             Avatar newAvatar = toAvatar(content, avatarRange);\r
911             this.integerList.add(entryNo);\r
912             this.avatarList.add(newAvatar);\r
913             return;\r
914         }\r
915 \r
916         /**\r
917          * {@inheritDoc}\r
918          * @param role {@inheritDoc}\r
919          * @param num {@inheritDoc}\r
920          * @throws HtmlParseException {@inheritDoc}\r
921          */\r
922         @Override\r
923         public void sysEventOpenRole(GameRole role, int num)\r
924                 throws HtmlParseException{\r
925             this.roleList.add(role);\r
926             this.integerList.add(num);\r
927             return;\r
928         }\r
929 \r
930         /**\r
931          * {@inheritDoc}\r
932          * @param content {@inheritDoc}\r
933          * @param avatarRange {@inheritDoc}\r
934          * @throws HtmlParseException {@inheritDoc}\r
935          */\r
936         @Override\r
937         public void sysEventMurdered(DecodedContent content,\r
938                                        SeqRange avatarRange)\r
939                 throws HtmlParseException{\r
940             Avatar murdered = toAvatar(content, avatarRange);\r
941             this.avatarList.add(murdered);\r
942             return;\r
943         }\r
944 \r
945         /**\r
946          * {@inheritDoc}\r
947          * @param content {@inheritDoc}\r
948          * @param avatarRange {@inheritDoc}\r
949          * @throws HtmlParseException {@inheritDoc}\r
950          */\r
951         @Override\r
952         public void sysEventSurvivor(DecodedContent content,\r
953                                        SeqRange avatarRange)\r
954                 throws HtmlParseException{\r
955             Avatar survivor = toAvatar(content, avatarRange);\r
956             this.avatarList.add(survivor);\r
957             return;\r
958         }\r
959 \r
960         /**\r
961          * {@inheritDoc}\r
962          * @param content {@inheritDoc}\r
963          * @param voteByRange {@inheritDoc}\r
964          * @param voteToRange {@inheritDoc}\r
965          * @throws HtmlParseException {@inheritDoc}\r
966          */\r
967         @Override\r
968         public void sysEventCounting(DecodedContent content,\r
969                                        SeqRange voteByRange,\r
970                                        SeqRange voteToRange)\r
971                 throws HtmlParseException{\r
972             if(voteByRange.isValid()){\r
973                 Avatar voteBy = toAvatar(content, voteByRange);\r
974                 this.avatarList.add(voteBy);\r
975             }\r
976             Avatar voteTo = toAvatar(content, voteToRange);\r
977             this.avatarList.add(voteTo);\r
978             return;\r
979         }\r
980 \r
981         /**\r
982          * {@inheritDoc}\r
983          * @param content {@inheritDoc}\r
984          * @param voteByRange {@inheritDoc}\r
985          * @param voteToRange {@inheritDoc}\r
986          * @throws HtmlParseException {@inheritDoc}\r
987          */\r
988         @Override\r
989         public void sysEventCounting2(DecodedContent content,\r
990                                         SeqRange voteByRange,\r
991                                         SeqRange voteToRange)\r
992                 throws HtmlParseException{\r
993             sysEventCounting(content, voteByRange, voteToRange);\r
994             return;\r
995         }\r
996 \r
997         /**\r
998          * {@inheritDoc}\r
999          * @param content {@inheritDoc}\r
1000          * @param avatarRange {@inheritDoc}\r
1001          * @throws HtmlParseException {@inheritDoc}\r
1002          */\r
1003         @Override\r
1004         public void sysEventSuddenDeath(DecodedContent content,\r
1005                                            SeqRange avatarRange)\r
1006                 throws HtmlParseException{\r
1007             Avatar suddenDeath = toAvatar(content, avatarRange);\r
1008             this.avatarList.add(suddenDeath);\r
1009             return;\r
1010         }\r
1011 \r
1012         /**\r
1013          * {@inheritDoc}\r
1014          * @param content {@inheritDoc}\r
1015          * @param avatarRange {@inheritDoc}\r
1016          * @param anchorRange {@inheritDoc}\r
1017          * @param loginRange {@inheritDoc}\r
1018          * @param isLiving {@inheritDoc}\r
1019          * @param role {@inheritDoc}\r
1020          * @throws HtmlParseException {@inheritDoc}\r
1021          */\r
1022         @Override\r
1023         public void sysEventPlayerList(DecodedContent content,\r
1024                                           SeqRange avatarRange,\r
1025                                           SeqRange anchorRange,\r
1026                                           SeqRange loginRange,\r
1027                                           boolean isLiving,\r
1028                                           GameRole role )\r
1029                 throws HtmlParseException{\r
1030             Avatar who = toAvatar(content, avatarRange);\r
1031 \r
1032             CharSequence anchor;\r
1033             if(anchorRange.isValid()){\r
1034                 anchor = this.converter.convert(content, anchorRange);\r
1035             }else{\r
1036                 anchor = "";\r
1037             }\r
1038             CharSequence account = this.converter\r
1039                                        .convert(content, loginRange);\r
1040 \r
1041             Integer liveOrDead;\r
1042             if(isLiving) liveOrDead = Integer.valueOf(1);\r
1043             else         liveOrDead = Integer.valueOf(0);\r
1044 \r
1045             this.avatarList.add(who);\r
1046             this.charseqList.add(anchor);\r
1047             this.charseqList.add(account);\r
1048             this.integerList.add(liveOrDead);\r
1049             this.roleList.add(role);\r
1050 \r
1051             return;\r
1052         }\r
1053 \r
1054         /**\r
1055          * {@inheritDoc}\r
1056          * @param content {@inheritDoc}\r
1057          * @param avatarRange {@inheritDoc}\r
1058          * @param votes {@inheritDoc}\r
1059          * @throws HtmlParseException {@inheritDoc}\r
1060          */\r
1061         @Override\r
1062         public void sysEventExecution(DecodedContent content,\r
1063                                         SeqRange avatarRange,\r
1064                                         int votes )\r
1065                 throws HtmlParseException{\r
1066             Avatar who = toAvatar(content, avatarRange);\r
1067 \r
1068             this.avatarList.add(who);\r
1069             this.integerList.add(votes);\r
1070 \r
1071             return;\r
1072         }\r
1073 \r
1074         /**\r
1075          * {@inheritDoc}\r
1076          * @param hour {@inheritDoc}\r
1077          * @param minute {@inheritDoc}\r
1078          * @param minLimit {@inheritDoc}\r
1079          * @param maxLimit {@inheritDoc}\r
1080          * @throws HtmlParseException {@inheritDoc}\r
1081          */\r
1082         @Override\r
1083         public void sysEventAskEntry(int hour, int minute,\r
1084                                        int minLimit, int maxLimit)\r
1085                 throws HtmlParseException{\r
1086             this.integerList.add(hour * 60 + minute);\r
1087             this.integerList.add(minLimit);\r
1088             this.integerList.add(maxLimit);\r
1089             return;\r
1090         }\r
1091 \r
1092         /**\r
1093          * {@inheritDoc}\r
1094          * @param hour {@inheritDoc}\r
1095          * @param minute {@inheritDoc}\r
1096          * @throws HtmlParseException {@inheritDoc}\r
1097          */\r
1098         @Override\r
1099         public void sysEventAskCommit(int hour, int minute)\r
1100                 throws HtmlParseException{\r
1101             this.integerList.add(hour * 60 + minute);\r
1102             return;\r
1103         }\r
1104 \r
1105         /**\r
1106          * {@inheritDoc}\r
1107          * @param content {@inheritDoc}\r
1108          * @param avatarRange {@inheritDoc}\r
1109          * @throws HtmlParseException {@inheritDoc}\r
1110          */\r
1111         @Override\r
1112         public void sysEventNoComment(DecodedContent content,\r
1113                                         SeqRange avatarRange)\r
1114                 throws HtmlParseException{\r
1115             Avatar noComAvatar = toAvatar(content, avatarRange);\r
1116             this.avatarList.add(noComAvatar);\r
1117             return;\r
1118         }\r
1119 \r
1120         /**\r
1121          * {@inheritDoc}\r
1122          * @param winner {@inheritDoc}\r
1123          * @param hour {@inheritDoc}\r
1124          * @param minute {@inheritDoc}\r
1125          * @throws HtmlParseException {@inheritDoc}\r
1126          */\r
1127         @Override\r
1128         public void sysEventStayEpilogue(Team winner, int hour, int minute)\r
1129                 throws HtmlParseException{\r
1130             GameRole role = null;\r
1131 \r
1132             switch(winner){\r
1133             case VILLAGE: role = GameRole.INNOCENT; break;\r
1134             case WOLF:    role = GameRole.WOLF;     break;\r
1135             case HAMSTER: role = GameRole.HAMSTER;  break;\r
1136             default: assert false; break;\r
1137             }\r
1138 \r
1139             this.roleList.add(role);\r
1140             this.integerList.add(hour * 60 + minute);\r
1141 \r
1142             return;\r
1143         }\r
1144 \r
1145         /**\r
1146          * {@inheritDoc}\r
1147          * @param content {@inheritDoc}\r
1148          * @param guardByRange {@inheritDoc}\r
1149          * @param guardToRange {@inheritDoc}\r
1150          * @throws HtmlParseException {@inheritDoc}\r
1151          */\r
1152         @Override\r
1153         public void sysEventGuard(DecodedContent content,\r
1154                                     SeqRange guardByRange,\r
1155                                     SeqRange guardToRange)\r
1156                 throws HtmlParseException{\r
1157             Avatar guardBy = toAvatar(content, guardByRange);\r
1158             Avatar guardTo = toAvatar(content, guardToRange);\r
1159             this.avatarList.add(guardBy);\r
1160             this.avatarList.add(guardTo);\r
1161             return;\r
1162         }\r
1163 \r
1164         /**\r
1165          * {@inheritDoc}\r
1166          * @param content {@inheritDoc}\r
1167          * @param judgeByRange {@inheritDoc}\r
1168          * @param judgeToRange {@inheritDoc}\r
1169          * @throws HtmlParseException {@inheritDoc}\r
1170          */\r
1171         @Override\r
1172         public void sysEventJudge(DecodedContent content,\r
1173                                     SeqRange judgeByRange,\r
1174                                     SeqRange judgeToRange)\r
1175                 throws HtmlParseException{\r
1176             Avatar judgeBy = toAvatar(content, judgeByRange);\r
1177             Avatar judgeTo = toAvatar(content, judgeToRange);\r
1178             this.avatarList.add(judgeBy);\r
1179             this.avatarList.add(judgeTo);\r
1180             return;\r
1181         }\r
1182 \r
1183         /**\r
1184          * {@inheritDoc}\r
1185          * @throws HtmlParseException {@inheritDoc}\r
1186          */\r
1187         @Override\r
1188         public void endSysEvent() throws HtmlParseException{\r
1189             SysEvent event = new SysEvent();\r
1190             event.setEventFamily(this.eventFamily);\r
1191             event.setSysEventType(this.sysEventType);\r
1192             event.setContent(this.eventContent);\r
1193             event.addAvatarList(this.avatarList);\r
1194             event.addRoleList(this.roleList);\r
1195             event.addIntegerList(this.integerList);\r
1196             event.addCharSequenceList(this.charseqList);\r
1197 \r
1198             this.period.addTopic(event);\r
1199 \r
1200             if(   this.sysEventType == SysEventType.MURDERED\r
1201                || this.sysEventType == SysEventType.NOMURDER ){\r
1202                 for(Topic topic : this.period.topicList){\r
1203                     if( ! (topic instanceof Talk) ) continue;\r
1204                     Talk talk = (Talk) topic;\r
1205                     if(talk.getTalkType() != TalkType.WOLFONLY) continue;\r
1206                     if( ! StringUtils\r
1207                          .isTerminated(talk.getDialog(),\r
1208                                        "!\u0020今日がお前の命日だ!") ){\r
1209                         continue;\r
1210                     }\r
1211                     talk.setCount(-1);\r
1212                     this.countMap.clear();\r
1213                 }\r
1214             }\r
1215 \r
1216             this.eventFamily = null;\r
1217             this.sysEventType = null;\r
1218             this.eventContent = null;\r
1219             this.avatarList.clear();\r
1220             this.roleList.clear();\r
1221             this.integerList.clear();\r
1222             this.charseqList.clear();\r
1223 \r
1224             return;\r
1225         }\r
1226 \r
1227         /**\r
1228          * {@inheritDoc}\r
1229          * @throws HtmlParseException {@inheritDoc}\r
1230          */\r
1231         @Override\r
1232         public void endParse() throws HtmlParseException{\r
1233             return;\r
1234         }\r
1235 \r
1236         // TODO 村名のチェックは不要か?\r
1237     }\r
1238 \r
1239 }\r