OSDN Git Service

remove subversion keyword
[jindolf/JinParser.git] / src / main / java / jp / sourceforge / jindolf / parser / SysEventParser.java
1 /*\r
2  * System event parser\r
3  *\r
4  * License : The MIT License\r
5  * Copyright(c) 2009 olyutorskii\r
6  */\r
7 \r
8 package jp.sourceforge.jindolf.parser;\r
9 \r
10 import java.util.regex.Pattern;\r
11 import jp.sourceforge.jindolf.corelib.EventFamily;\r
12 import jp.sourceforge.jindolf.corelib.GameRole;\r
13 import jp.sourceforge.jindolf.corelib.SysEventType;\r
14 import jp.sourceforge.jindolf.corelib.Team;\r
15 \r
16 /**\r
17  * 人狼BBSシステムが出力する各種イベント表記のパースを行うパーサ。\r
18  * パース進行に従い{@link SysEventHandler}の各種メソッドが呼び出される。\r
19  */\r
20 public class SysEventParser extends AbstractParser{\r
21 \r
22     private static final String AVATAR_REGEX =\r
23             "[^<、" + SPCHAR + "]+\u0020[^<、。" + SPCHAR + "]+";\r
24 \r
25     private static final Pattern C_DIV_PATTERN =\r
26             compile(SP_I+ "</div>" +SP_I);\r
27     private static final Pattern AVATAR_PATTERN =\r
28             compile(AVATAR_REGEX);\r
29 \r
30 \r
31     private SysEventHandler sysEventHandler;\r
32 \r
33     private int pushedRegionStart = -1;\r
34     private int pushedRegionEnd   = -1;\r
35 \r
36     private final SeqRange rangepool_1 = new SeqRange();\r
37     private final SeqRange rangepool_2 = new SeqRange();\r
38     private final SeqRange rangepool_3 = new SeqRange();\r
39 \r
40     /**\r
41      * コンストラクタ。\r
42      * @param parent 親パーサ\r
43      */\r
44     public SysEventParser(ChainedParser parent){\r
45         super(parent);\r
46         return;\r
47     }\r
48 \r
49     /**\r
50      * {@link SysEventHandler}ハンドラを登録する。\r
51      * @param sysEventHandler ハンドラ\r
52      */\r
53     public void setSysEventHandler(SysEventHandler sysEventHandler){\r
54         this.sysEventHandler = sysEventHandler;\r
55         return;\r
56     }\r
57 \r
58     /**\r
59      * Announceメッセージをパースする。\r
60      * @throws HtmlParseException パースエラー\r
61      */\r
62     public void parseAnnounce() throws HtmlParseException{\r
63         setContextErrorMessage("Unknown Announce message");\r
64 \r
65         this.sysEventHandler.startSysEvent(EventFamily.ANNOUNCE);\r
66 \r
67         int regionStart = regionStart();\r
68         int regionEnd   = regionEnd();\r
69 \r
70         boolean result =\r
71                    probeSimpleAnnounce()\r
72                 || probeOpenRole()\r
73                 || probeSurvivor()\r
74                 || probeMurdered()\r
75                 || probeOnStage()\r
76                 || probeSuddenDeath()\r
77                 || probeCounting()\r
78                 || probePlayerList()\r
79                 || probeExecution()\r
80                 || probeVanish()\r
81                 ;\r
82         if( ! result ){\r
83             throw buildParseException();\r
84         }\r
85 \r
86         getMatcher().region(regionStart, regionEnd);\r
87         parseContent();\r
88 \r
89         lookingAtAffirm(C_DIV_PATTERN);\r
90         shrinkRegion();\r
91 \r
92         this.sysEventHandler.endSysEvent();\r
93 \r
94         return;\r
95     }\r
96 \r
97     private static final Pattern STARTENTRY_PATTERN =\r
98              compile(\r
99              "昼間は人間のふりをして、夜に正体を現すという人狼。<br />"\r
100             +"その人狼が、"\r
101             +"この村に紛れ込んでいるという噂が広がった。<br /><br />"\r
102             +"村人達は半信半疑ながらも、"\r
103             +"村はずれの宿に集められることになった。"\r
104             +"<br />");\r
105     private static final Pattern STARTMIRROR_PATTERN =\r
106              compile(\r
107              "さあ、自らの姿を鏡に映してみよう。<br />"\r
108             +"そこに映るのはただの村人か、"\r
109             +"それとも血に飢えた人狼か。<br /><br />"\r
110             +"例え人狼でも、多人数で立ち向かえば怖くはない。<br />"\r
111             +"問題は、だれが人狼なのかという事だ。<br />"\r
112             +"占い師の能力を持つ人間ならば、それを見破れるだろう。"\r
113             +"(?:<br />)?");\r
114     private static final Pattern STARTASSAULT_PATTERN =\r
115              compile(\r
116              "ついに犠牲者が出た。人狼はこの村人達のなかにいる。<br />"\r
117             +"しかし、それを見分ける手段はない。<br /><br />"\r
118             +"村人達は、疑わしい者を排除するため、"\r
119             +"投票を行う事にした。<br />"\r
120             +"無実の犠牲者が出るのもやむをえない。"\r
121             +"村が全滅するよりは……。<br /><br />"\r
122             +"最後まで残るのは村人か、それとも人狼か。"\r
123             +"(?:<br />)?");\r
124     private static final Pattern NOMURDER_PATTERN =\r
125              compile(\r
126              "今日は犠牲者がいないようだ。人狼は襲撃に失敗したのだろうか。");\r
127     private static final Pattern WINVILLAGE_PATTERN =\r
128              compile(\r
129              "全ての人狼を退治した……。人狼に怯える日々は去ったのだ!"\r
130             +"(?:<br />)?");\r
131     private static final Pattern WINWOLF_PATTERN =\r
132              compile(\r
133              "もう人狼に抵抗できるほど村人は残っていない……。<br />"\r
134             +"人狼は残った村人を全て食らい、"\r
135             +"別の獲物を求めてこの村を去っていった。"\r
136             +"(?:<br />)?");\r
137     private static final Pattern WINHAMSTER_PATTERN =\r
138              compile(\r
139               "全ては終わったかのように見えた。<br />"\r
140              +"だが、奴が生き残っていた……。");\r
141     private static final Pattern PANIC_PATTERN =\r
142             compile("……。");\r
143 \r
144     private static Object[][] simpleRegexToType = {\r
145         { STARTENTRY_PATTERN,   SysEventType.STARTENTRY   },\r
146         { STARTMIRROR_PATTERN,  SysEventType.STARTMIRROR  },\r
147         { STARTASSAULT_PATTERN, SysEventType.STARTASSAULT },\r
148         { NOMURDER_PATTERN,     SysEventType.NOMURDER     },\r
149         { WINVILLAGE_PATTERN,   SysEventType.WINVILLAGE   },\r
150         { WINWOLF_PATTERN,      SysEventType.WINWOLF      },\r
151         { WINHAMSTER_PATTERN,   SysEventType.WINHAMSTER   },\r
152         { PANIC_PATTERN,        SysEventType.PANIC        },\r
153     };\r
154 \r
155     /**\r
156      * 文字列が固定されたシンプルなAnnounceメッセージのパースを試みる。\r
157      * @return マッチしたらtrue\r
158      * @throws HtmlParseException パースエラー\r
159      */\r
160     private boolean probeSimpleAnnounce() throws HtmlParseException{\r
161         pushRegion();\r
162 \r
163         sweepSpace();\r
164 \r
165         SysEventType matchedType = null;\r
166 \r
167         for(Object[] pair : simpleRegexToType){\r
168             Pattern pattern = (Pattern)( pair[0] );\r
169 \r
170             if(lookingAtProbe(pattern)){\r
171                 shrinkRegion();\r
172                 matchedType = (SysEventType)( pair[1] );\r
173                 break;\r
174             }\r
175         }\r
176 \r
177         if(matchedType == null){\r
178             popRegion();\r
179             return false;\r
180         }\r
181 \r
182         this.sysEventHandler.sysEventType(matchedType);\r
183 \r
184         sweepSpace();\r
185 \r
186         return true;\r
187     }\r
188 \r
189     private static final Pattern OPENROLE_HEAD_PATTERN =\r
190             compile("どうやらこの中には、");\r
191     private static final Pattern OPENROLE_NUM_PATTERN =\r
192             compile("が([0-9]+)名(?:、)?");\r
193     private static final Pattern OPENROLE_TAIL_PATTERN =\r
194             compile("いるようだ。");\r
195 \r
196     /**\r
197      * OPENROLEメッセージのパースを試みる。\r
198      * @return マッチしたらtrue\r
199      * @throws HtmlParseException パースエラー\r
200      */\r
201     private boolean probeOpenRole() throws HtmlParseException{\r
202         pushRegion();\r
203 \r
204         sweepSpace();\r
205 \r
206         if( ! lookingAtProbe(OPENROLE_HEAD_PATTERN) ){\r
207             popRegion();\r
208             return false;\r
209         }\r
210         shrinkRegion();\r
211 \r
212         this.sysEventHandler.sysEventType(SysEventType.OPENROLE);\r
213 \r
214         for(;;){\r
215             GameRole role = lookingAtRole();\r
216             if(role == null){\r
217                 if( lookingAtProbe(OPENROLE_TAIL_PATTERN) ){\r
218                     shrinkRegion();\r
219                     break;\r
220                 }\r
221                 popRegion();\r
222                 return false;\r
223             }\r
224             shrinkRegion();\r
225 \r
226             if( ! lookingAtProbe(OPENROLE_NUM_PATTERN) ){\r
227                 popRegion();\r
228                 return false;\r
229             }\r
230             int num = parseGroupedInt(1);\r
231             shrinkRegion();\r
232 \r
233             this.sysEventHandler.sysEventOpenRole(role, num);\r
234         }\r
235 \r
236         sweepSpace();\r
237 \r
238         return true;\r
239     }\r
240 \r
241     private static final Pattern SURVIVOR_HEAD_PATTERN =\r
242             compile("現在の生存者は、");\r
243     private static final Pattern SURVIVOR_PATTERN =\r
244             Pattern.compile(\r
245             "(" + AVATAR_REGEX + ")"\r
246             +"(?:"\r
247                 +"(?:"\r
248                     +"、"\r
249                 +")|(?:"\r
250                     +"\u0020の\u0020([0-9]+)\u0020名。"\r
251                 +")"\r
252             +")");\r
253 \r
254     /**\r
255      * SURVIVORメッセージのパースを試みる。\r
256      * @return マッチしたらtrue\r
257      * @throws HtmlParseException パースエラー\r
258      */\r
259     private boolean probeSurvivor() throws HtmlParseException{\r
260         SeqRange avatarRange = this.rangepool_1;\r
261 \r
262         pushRegion();\r
263 \r
264         sweepSpace();\r
265 \r
266         if( ! lookingAtProbe(SURVIVOR_HEAD_PATTERN) ){\r
267             popRegion();\r
268             return false;\r
269         }\r
270         shrinkRegion();\r
271 \r
272         this.sysEventHandler.sysEventType(SysEventType.SURVIVOR);\r
273 \r
274         int avatarNum = 0;\r
275         for(;;){\r
276             if( ! lookingAtProbe(SURVIVOR_PATTERN) ){\r
277                 popRegion();\r
278                 return false;\r
279             }\r
280             avatarRange.setLastMatchedGroupRange(getMatcher(), 1);\r
281             this.sysEventHandler\r
282                 .sysEventSurvivor(getContent(), avatarRange);\r
283             avatarNum++;\r
284             if(isGroupMatched(2)){\r
285                 int num = parseGroupedInt(2);\r
286                 shrinkRegion();\r
287                 if(num != avatarNum){\r
288                     throw new HtmlParseException(regionStart());\r
289                 }\r
290                 break;\r
291             }\r
292             shrinkRegion();\r
293         }\r
294 \r
295         sweepSpace();\r
296 \r
297         return true;\r
298     }\r
299 \r
300     private static final Pattern MURDERED_HEAD_PATTERN =\r
301             compile("次の日の朝、");\r
302     private static final Pattern MURDERED_SW_PATTERN =\r
303             compile(\r
304                 "("\r
305                     +"\u0020と\u0020"\r
306                 +")|("\r
307                     +"\u0020が無残な姿で発見された。"\r
308                     +"(?:<br />)?"  // E国対策\r
309                 +")"\r
310             );\r
311 \r
312     /**\r
313      * MURDEREDメッセージのパースを試みる。\r
314      * @return マッチしたらtrue\r
315      * @throws HtmlParseException パースエラー\r
316      */\r
317     private boolean probeMurdered() throws HtmlParseException{\r
318         SeqRange avatarRange  = this.rangepool_1;\r
319         SeqRange avatarRange2 = this.rangepool_2;\r
320         avatarRange .setInvalid();\r
321         avatarRange2.setInvalid();\r
322 \r
323         pushRegion();\r
324 \r
325         sweepSpace();\r
326 \r
327         if( ! lookingAtProbe(MURDERED_HEAD_PATTERN)){\r
328             popRegion();\r
329             return false;\r
330         }\r
331         shrinkRegion();\r
332 \r
333         this.sysEventHandler.sysEventType(SysEventType.MURDERED);\r
334 \r
335         for(;;){\r
336             if( ! lookingAtProbe(AVATAR_PATTERN)){\r
337                 popRegion();\r
338                 return false;\r
339             }\r
340             if( ! avatarRange.isValid() ){\r
341                 avatarRange.setLastMatchedRange(getMatcher());\r
342             }else if( ! avatarRange2.isValid() ){\r
343                 avatarRange2.setLastMatchedRange(getMatcher());\r
344             }else{\r
345                 assert false;\r
346                 throw buildParseException();\r
347             }\r
348             shrinkRegion();\r
349 \r
350             if( ! lookingAtProbe(MURDERED_SW_PATTERN)){\r
351                 popRegion();\r
352                 return false;\r
353             }\r
354             if(isGroupMatched(1)){\r
355                 shrinkRegion();\r
356                 continue;\r
357             }else if(isGroupMatched(2)){\r
358                 shrinkRegion();\r
359                 break;\r
360             }else{\r
361                 assert false;\r
362                 throw buildParseException();\r
363             }\r
364         }\r
365 \r
366         this.sysEventHandler\r
367             .sysEventMurdered(getContent(), avatarRange);\r
368         if(avatarRange2.isValid()){\r
369             this.sysEventHandler\r
370                 .sysEventMurdered(getContent(), avatarRange2);\r
371         }\r
372 \r
373         sweepSpace();\r
374 \r
375         return true;\r
376     }\r
377 \r
378     private static final Pattern ONSTAGE_NO_PATTERN =\r
379             compile("([0-9]+)人目、");\r
380     private static final Pattern ONSTAGE_DOT_PATTERN =\r
381             compile(\r
382              "("\r
383             +"(?:" + AVATAR_REGEX + ")"\r
384             +"|)"    // F1556プロローグ対策\r
385             +"。");\r
386 \r
387     /**\r
388      * ONSTAGEメッセージのパースを試みる。\r
389      * @return マッチしたらtrue\r
390      * @throws HtmlParseException パースエラー\r
391      */\r
392     private boolean probeOnStage() throws HtmlParseException{\r
393         SeqRange avatarRange = this.rangepool_1;\r
394 \r
395         pushRegion();\r
396 \r
397         sweepSpace();\r
398 \r
399         if( ! lookingAtProbe(ONSTAGE_NO_PATTERN) ){\r
400             popRegion();\r
401             return false;\r
402         }\r
403         int entryNo = parseGroupedInt(1);\r
404         shrinkRegion();\r
405 \r
406         this.sysEventHandler.sysEventType(SysEventType.ONSTAGE);\r
407 \r
408         if( ! lookingAtProbe(ONSTAGE_DOT_PATTERN) ){\r
409             popRegion();\r
410             return false;\r
411         }\r
412         avatarRange.setLastMatchedGroupRange(getMatcher(), 1);\r
413         shrinkRegion();\r
414 \r
415         this.sysEventHandler\r
416             .sysEventOnStage(getContent(), entryNo, avatarRange);\r
417 \r
418         sweepSpace();\r
419 \r
420         return true;\r
421     }\r
422 \r
423     private static final Pattern SUDDENDEATH_PATTERN =\r
424             compile(\r
425                  "("\r
426                 +"(?:" + AVATAR_REGEX + ")"\r
427                 +"|)"                            // F681 2d 対策\r
428                 +"\u0020?は、突然死した。"\r
429             );\r
430 \r
431     /**\r
432      * SUDDENDEATHメッセージのパースを試みる。\r
433      * @return マッチしたらtrue\r
434      * @throws HtmlParseException パースエラー\r
435      */\r
436     private boolean probeSuddenDeath() throws HtmlParseException{\r
437         SeqRange avatarRange = this.rangepool_1;\r
438 \r
439         pushRegion();\r
440 \r
441         sweepSpace();\r
442 \r
443         if( ! lookingAtProbe(SUDDENDEATH_PATTERN)){\r
444             popRegion();\r
445             return false;\r
446         }\r
447         avatarRange.setLastMatchedGroupRange(getMatcher(), 1);\r
448         shrinkRegion();\r
449 \r
450         this.sysEventHandler.sysEventType(SysEventType.SUDDENDEATH);\r
451         this.sysEventHandler\r
452             .sysEventSuddenDeath(getContent(), avatarRange);\r
453 \r
454         sweepSpace();\r
455 \r
456         return true;\r
457     }\r
458 \r
459     private static final Pattern COUNTING_PATTERN =\r
460             compile(\r
461             "(?:"\r
462                 +"<br />"\r
463                 +"(" + AVATAR_REGEX + ")"\r
464                 +"\u0020は村人達の手により処刑された。"\r
465             +")|(?:"\r
466                 +"(" + AVATAR_REGEX + ")"\r
467                 +"\u0020は\u0020"\r
468                 +"(" + AVATAR_REGEX + ")"\r
469                 +"\u0020に投票した。"\r
470                 +"(?:<br />)?"\r
471             +")"\r
472             );\r
473 \r
474     /**\r
475      * COUNTINGメッセージのパースを試みる。\r
476      * @return マッチしたらtrue\r
477      * @throws HtmlParseException パースエラー\r
478      */\r
479     private boolean probeCounting() throws HtmlParseException{\r
480         SeqRange voteByRange = this.rangepool_1;\r
481         SeqRange voteToRange = this.rangepool_2;\r
482 \r
483         pushRegion();\r
484 \r
485         sweepSpace();\r
486 \r
487         boolean hasVote = false;\r
488         for(;;){\r
489             if( ! lookingAtProbe(COUNTING_PATTERN) ){\r
490                 break; // 処刑なし\r
491             }\r
492             if(isGroupMatched(1)){\r
493                 voteByRange.setInvalid();\r
494                 voteToRange.setLastMatchedGroupRange(getMatcher(), 1);\r
495                 shrinkRegion();\r
496                 this.sysEventHandler\r
497                     .sysEventCounting(getContent(),\r
498                                       voteByRange,\r
499                                       voteToRange );\r
500                 break;\r
501             }else if(isGroupMatched(2)){\r
502                 if( ! hasVote ){\r
503                     hasVote = true;\r
504                     this.sysEventHandler.sysEventType(SysEventType.COUNTING);\r
505                 }\r
506                 voteByRange.setLastMatchedGroupRange(getMatcher(), 2);\r
507                 voteToRange.setLastMatchedGroupRange(getMatcher(), 3);\r
508                 shrinkRegion();\r
509                 this.sysEventHandler\r
510                     .sysEventCounting(getContent(),\r
511                                       voteByRange,\r
512                                       voteToRange );\r
513             }else{\r
514                 assert false;\r
515                 throw buildParseException();\r
516             }\r
517         }\r
518 \r
519         if( ! hasVote ){\r
520             popRegion();\r
521             return false;\r
522         }\r
523 \r
524         sweepSpace();\r
525 \r
526         return true;\r
527     }\r
528 \r
529     private static final Pattern COUNTING2_PATTERN =\r
530             compile(\r
531                  "(" + AVATAR_REGEX + ")"\r
532                 +"\u0020は\u0020"\r
533                 +"(" + AVATAR_REGEX + ")"\r
534                 +"\u0020に投票した。"\r
535                 +"(?:<br />)?"\r
536             );\r
537 \r
538     /**\r
539      * COUNTING2メッセージのパースを試みる。\r
540      * @return マッチしたらtrue\r
541      * @throws HtmlParseException パースエラー\r
542      */\r
543     private boolean probeCounting2() throws HtmlParseException{\r
544         SeqRange voteByRange = this.rangepool_1;\r
545         SeqRange voteToRange = this.rangepool_2;\r
546 \r
547         pushRegion();\r
548 \r
549         sweepSpace();\r
550 \r
551         boolean hasVote = false;\r
552         for(;;){\r
553             if( ! lookingAtProbe(COUNTING2_PATTERN) ){\r
554                 break;\r
555             }\r
556             if( ! hasVote ){\r
557                 hasVote = true;\r
558                 this.sysEventHandler.sysEventType(SysEventType.COUNTING2);\r
559             }\r
560             voteByRange.setLastMatchedGroupRange(getMatcher(), 1);\r
561             voteToRange.setLastMatchedGroupRange(getMatcher(), 2);\r
562             shrinkRegion();\r
563             this.sysEventHandler\r
564                 .sysEventCounting2(getContent(),\r
565                                    voteByRange,\r
566                                    voteToRange );\r
567         }\r
568 \r
569         if( ! hasVote ){\r
570             popRegion();\r
571             return false;\r
572         }\r
573 \r
574         sweepSpace();\r
575 \r
576         return true;\r
577     }\r
578 \r
579     private static final Pattern PLAYERID_PATTERN =\r
580             compile(\r
581                 "\u0020\uff08" // 全角開き括弧\r
582                 +"(?:<a\u0020href=\"([^\"]*)\">)?"\r
583                 +"([^<]*)"\r
584                 +"(?:</a>)?"\r
585                 +"\uff09、"     // 全角閉じ括弧\r
586             );\r
587     private static final Pattern LIVEORDIE_PATTERN =\r
588             compile(\r
589                 "(生存。)|(死亡。)"\r
590             );\r
591     private static final Pattern PLAYER_DELIM_PATTERN =\r
592             compile(\r
593                  "だった。"\r
594                 +"(?:<br />)?"\r
595             );\r
596 \r
597     /**\r
598      * PLAYERLISTメッセージのパースを試みる。\r
599      * @return マッチしたらtrue\r
600      * @throws HtmlParseException パースエラー\r
601      */\r
602     private boolean probePlayerList() throws HtmlParseException{\r
603         SeqRange avatarRange  = this.rangepool_1;\r
604         SeqRange anchorRange  = this.rangepool_2;\r
605         SeqRange accountRange = this.rangepool_3;\r
606 \r
607         pushRegion();\r
608 \r
609         sweepSpace();\r
610 \r
611         boolean hasPlayerList = false;\r
612 \r
613         for(;;){\r
614             if( ! lookingAtProbe(AVATAR_PATTERN)){\r
615                 break;\r
616             }\r
617             avatarRange.setLastMatchedRange(getMatcher());\r
618             shrinkRegion();\r
619 \r
620             if( ! lookingAtProbe(PLAYERID_PATTERN)){\r
621                 popRegion();\r
622                 return false;\r
623             }\r
624             if(isGroupMatched(1)){\r
625                 anchorRange.setLastMatchedGroupRange(getMatcher(), 1);\r
626             }else{\r
627                 anchorRange.setInvalid();\r
628             }\r
629             accountRange.setLastMatchedGroupRange(getMatcher(), 2);\r
630             shrinkRegion();\r
631 \r
632             boolean isLiving = false;\r
633             if( ! lookingAtProbe(LIVEORDIE_PATTERN)){\r
634                 popRegion();\r
635                 return false;\r
636             }\r
637             if(isGroupMatched(1)){\r
638                 isLiving = true;\r
639             }else if(isGroupMatched(2)){\r
640                 isLiving = false;\r
641             }\r
642             shrinkRegion();\r
643 \r
644             GameRole role = lookingAtRole();\r
645             if(role == null){\r
646                 popRegion();\r
647                 return false;\r
648             }\r
649             shrinkRegion();\r
650 \r
651             if( ! lookingAtProbe(PLAYER_DELIM_PATTERN)){\r
652                 popRegion();\r
653                 return false;\r
654             }\r
655             shrinkRegion();\r
656 \r
657             if( ! hasPlayerList ){\r
658                 hasPlayerList = true;\r
659                 this.sysEventHandler.sysEventType(SysEventType.PLAYERLIST);\r
660             }\r
661 \r
662             this.sysEventHandler\r
663                 .sysEventPlayerList(getContent(),\r
664                                     avatarRange,\r
665                                     anchorRange,\r
666                                     accountRange,\r
667                                     isLiving,\r
668                                     role );\r
669         }\r
670 \r
671         if( ! hasPlayerList ){\r
672             popRegion();\r
673             return false;\r
674         }\r
675 \r
676         sweepSpace();\r
677 \r
678         return true;\r
679     }\r
680 \r
681     private static final Pattern EXECUTION_PATTERN =\r
682             compile(\r
683                 "(?:"\r
684                 + "(" + AVATAR_REGEX + ")、([0-9]+)票。(?:<br />)?"\r
685                 +")|(?:"\r
686                 +"<br />(" + AVATAR_REGEX + ")\u0020は"\r
687                 +"村人達の手により処刑された。"\r
688                 +")"\r
689             );\r
690 \r
691     /**\r
692      * EXECUTIONメッセージのパースを試みる。\r
693      * @return マッチしたらtrue\r
694      * @throws HtmlParseException パースエラー\r
695      */\r
696     private boolean probeExecution() throws HtmlParseException{\r
697         SeqRange avatarRange  = this.rangepool_1;\r
698 \r
699         pushRegion();\r
700 \r
701         sweepSpace();\r
702 \r
703         boolean hasExecution = false;\r
704 \r
705         for(;;){\r
706             if( ! lookingAtProbe(EXECUTION_PATTERN)){\r
707                 break;\r
708             }\r
709 \r
710             if( ! hasExecution ){\r
711                 hasExecution = true;\r
712                 this.sysEventHandler.sysEventType(SysEventType.EXECUTION);\r
713             }\r
714 \r
715             if(isGroupMatched(1)){\r
716                 avatarRange.setLastMatchedGroupRange(getMatcher(), 1);\r
717                 int votes = parseGroupedInt(2);\r
718                 shrinkRegion();\r
719                 this.sysEventHandler\r
720                     .sysEventExecution(getContent(),\r
721                                        avatarRange,\r
722                                        votes );\r
723             }else if(isGroupMatched(3)){\r
724                 avatarRange.setLastMatchedGroupRange(getMatcher(), 3);\r
725                 shrinkRegion();\r
726                 this.sysEventHandler\r
727                     .sysEventExecution(getContent(),\r
728                                        avatarRange,\r
729                                        -1 );\r
730             }\r
731         }\r
732 \r
733         if( ! hasExecution ){\r
734             popRegion();\r
735             return false;\r
736         }\r
737 \r
738         sweepSpace();\r
739 \r
740         return true;\r
741     }\r
742 \r
743     private static final Pattern VANISH_PATTERN =\r
744             compile(\r
745                  "(?:<br />)*"\r
746                 +"(" + AVATAR_REGEX + ")"\r
747                 +"\u0020は、失踪した。"\r
748                 +"(?:<br />)*"\r
749             );\r
750 \r
751     /**\r
752      * VANISHメッセージのパースを試みる。\r
753      * @return マッチしたらtrue\r
754      * @throws HtmlParseException パースエラー\r
755      */\r
756     private boolean probeVanish() throws HtmlParseException{\r
757         SeqRange avatarRange  = this.rangepool_1;\r
758 \r
759         pushRegion();\r
760 \r
761         sweepSpace();\r
762 \r
763         boolean hasVanish = false;\r
764 \r
765         for(;;){\r
766             if( ! lookingAtProbe(VANISH_PATTERN)){\r
767                 break;\r
768             }\r
769 \r
770             if( ! hasVanish ){\r
771                 hasVanish = true;\r
772                 this.sysEventHandler.sysEventType(SysEventType.VANISH);\r
773             }\r
774             avatarRange.setLastMatchedGroupRange(getMatcher(), 1);\r
775 \r
776             shrinkRegion();\r
777 \r
778             this.sysEventHandler\r
779                 .sysEventVanish(getContent(), avatarRange);\r
780         }\r
781 \r
782         if( ! hasVanish ){\r
783             popRegion();\r
784             return false;\r
785         }\r
786 \r
787         sweepSpace();\r
788 \r
789         return true;\r
790     }\r
791 \r
792     /**\r
793      * Orderメッセージをパースする。\r
794      * @throws HtmlParseException パースエラー\r
795      */\r
796     public void parseOrder() throws HtmlParseException{\r
797         setContextErrorMessage("Unknown Order message");\r
798 \r
799         this.sysEventHandler.startSysEvent(EventFamily.ORDER);\r
800 \r
801         int regionStart = regionStart();\r
802         int regionEnd   = regionEnd();\r
803 \r
804         boolean result =\r
805                    probeAskEntry()\r
806                 || probeAskCommit()\r
807                 || probeNoComment()\r
808                 || probeStayEpilogue()\r
809                 || probeGameOver()\r
810                 ;\r
811         if( ! result ){\r
812             throw buildParseException();\r
813         }\r
814 \r
815         getMatcher().region(regionStart, regionEnd);\r
816         parseContent();\r
817 \r
818         lookingAtAffirm(C_DIV_PATTERN);\r
819         shrinkRegion();\r
820 \r
821         this.sysEventHandler.endSysEvent();\r
822 \r
823         return;\r
824     }\r
825 \r
826     private static final Pattern ASKENTRY_PATTERN =\r
827             compile(\r
828              "演じたいキャラクターを選び、発言してください。<br />"\r
829             +"([0-2][0-9]):([0-5][0-9])\u0020に"\r
830             +"([0-9]+)名以上がエントリーしていれば進行します。<br />"\r
831             +"最大([0-9]+)名まで参加可能です。<br /><br />"\r
832             +"※[\u0020]?エントリーは取り消せません。"\r
833             +"ルールをよく理解した上でご参加下さい。<br />"\r
834             +"(?:※始めての方は、村人希望での参加となります。<br />)?"\r
835             +"(?:※希望能力についての発言は控えてください。<br />)?"\r
836             );\r
837 \r
838     /**\r
839      * ASKENTRYメッセージのパースを試みる。\r
840      * @return マッチしたらtrue\r
841      * @throws HtmlParseException パースエラー\r
842      */\r
843     private boolean probeAskEntry() throws HtmlParseException{\r
844         pushRegion();\r
845 \r
846         sweepSpace();\r
847 \r
848         if( ! lookingAtProbe(ASKENTRY_PATTERN)){\r
849             popRegion();\r
850             return false;\r
851         }\r
852 \r
853         int hour     = parseGroupedInt(1);\r
854         int minute   = parseGroupedInt(2);\r
855         int minLimit = parseGroupedInt(3);\r
856         int maxLimit = parseGroupedInt(4);\r
857 \r
858         shrinkRegion();\r
859 \r
860         this.sysEventHandler.sysEventType(SysEventType.ASKENTRY);\r
861         this.sysEventHandler\r
862             .sysEventAskEntry(hour, minute, minLimit, maxLimit);\r
863 \r
864         sweepSpace();\r
865 \r
866         return true;\r
867     }\r
868 \r
869     private static final Pattern ASKCOMMIT_PATTERN =\r
870             compile(\r
871              "(?:"\r
872             +"([0-2][0-9]):([0-5][0-9])\u0020までに、"\r
873             +"誰を処刑するべきかの投票先を決定して下さい。<br />"\r
874             +"一番票を集めた人物が処刑されます。"\r
875             +"同数だった場合はランダムで決定されます。<br /><br />"\r
876             +")?"\r
877             +"特殊な能力を持つ人は、"\r
878             +"([0-2][0-9]):([0-5][0-9])\u0020までに"\r
879             +"行動を確定して下さい。<br />"\r
880             );\r
881 \r
882     /**\r
883      * ASKCOMMITメッセージのパースを試みる。\r
884      * @return マッチしたらtrue\r
885      * @throws HtmlParseException パースエラー\r
886      */\r
887     private boolean probeAskCommit() throws HtmlParseException{\r
888         pushRegion();\r
889 \r
890         sweepSpace();\r
891 \r
892         if( ! lookingAtProbe(ASKCOMMIT_PATTERN)){\r
893             popRegion();\r
894             return false;\r
895         }\r
896 \r
897         boolean is1stDay;\r
898         if(isGroupMatched(1)){\r
899             is1stDay = false;\r
900         }else{\r
901             is1stDay = true;\r
902         }\r
903 \r
904         int hh1 = parseGroupedInt(1);\r
905         int mm1 = parseGroupedInt(2);\r
906         int hh2 = parseGroupedInt(3);\r
907         int mm2 = parseGroupedInt(4);\r
908 \r
909         shrinkRegion();\r
910 \r
911         if( ! is1stDay && (hh1 != hh2 || mm1 != mm2) ){\r
912             throw new HtmlParseException(regionStart());\r
913         }\r
914 \r
915         this.sysEventHandler.sysEventType(SysEventType.ASKCOMMIT);\r
916         this.sysEventHandler.sysEventAskCommit(hh2, mm2);\r
917 \r
918         sweepSpace();\r
919 \r
920         return true;\r
921     }\r
922 \r
923     private static final Pattern NOCOMMENT_HEAD_PATTERN =\r
924             compile("本日まだ発言していない者は、");\r
925     private static final Pattern NOCOMMENT_AVATAR_PATTERN =\r
926             compile(\r
927              "(?:"\r
928                 +"(" + AVATAR_REGEX + ")、"\r
929             +")|(?:"\r
930                 +"以上\u0020([0-9]+)\u0020名。"\r
931             +")"\r
932             );\r
933 \r
934     /**\r
935      * NOCOMMENTメッセージのパースを試みる。\r
936      * @return マッチしたらtrue\r
937      * @throws HtmlParseException パースエラー\r
938      */\r
939     private boolean probeNoComment() throws HtmlParseException{\r
940         SeqRange avatarRange = this.rangepool_1;\r
941 \r
942         pushRegion();\r
943 \r
944         sweepSpace();\r
945 \r
946         if( ! lookingAtProbe(NOCOMMENT_HEAD_PATTERN)){\r
947             popRegion();\r
948             return false;\r
949         }\r
950         shrinkRegion();\r
951 \r
952         this.sysEventHandler.sysEventType(SysEventType.NOCOMMENT);\r
953 \r
954         int avatarNum = 0;\r
955         for(;;){\r
956             if( ! lookingAtProbe(NOCOMMENT_AVATAR_PATTERN)){\r
957                 popRegion();\r
958                 return false;\r
959             }\r
960 \r
961             if(isGroupMatched(1)){\r
962                 avatarRange.setLastMatchedGroupRange(getMatcher(), 1);\r
963                 this.sysEventHandler\r
964                     .sysEventNoComment(getContent(), avatarRange);\r
965                 shrinkRegion();\r
966                 avatarNum++;\r
967             }else if(isGroupMatched(2)){\r
968                 int num = parseGroupedInt(2);\r
969                 shrinkRegion();\r
970                 if(num != avatarNum){\r
971                     throw new HtmlParseException(regionStart());\r
972                 }\r
973                 break;\r
974             }\r
975         }\r
976 \r
977         sweepSpace();\r
978 \r
979         return true;\r
980     }\r
981 \r
982     private static final Pattern STAYEPILOGUE_PATTERN =\r
983             compile(\r
984             "(?:(村人)|(人狼)|(ハムスター))側の勝利です!<br />"\r
985             +"全てのログとユーザー名を公開します。"\r
986             +"([0-2][0-9]):([0-5][0-9])\u0020まで"\r
987             +"自由に書き込めますので、"\r
988             +"今回の感想などをどうぞ。<br />"\r
989             );\r
990 \r
991     /**\r
992      * STAYEPILOGUEメッセージのパースを試みる。\r
993      * @return マッチしたらtrue\r
994      * @throws HtmlParseException パースエラー\r
995      */\r
996     private boolean probeStayEpilogue() throws HtmlParseException{\r
997         pushRegion();\r
998 \r
999         sweepSpace();\r
1000 \r
1001         if( ! lookingAtProbe(STAYEPILOGUE_PATTERN)){\r
1002             popRegion();\r
1003             return false;\r
1004         }\r
1005 \r
1006         Team winner = null;\r
1007         if(isGroupMatched(1)){\r
1008             winner = Team.VILLAGE;\r
1009         }else if(isGroupMatched(2)){\r
1010             winner = Team.WOLF;\r
1011         }else if(isGroupMatched(3)){\r
1012             winner = Team.HAMSTER;\r
1013         }\r
1014 \r
1015         int hour = parseGroupedInt(4);\r
1016         int minute = parseGroupedInt(5);\r
1017 \r
1018         shrinkRegion();\r
1019 \r
1020         this.sysEventHandler.sysEventType(SysEventType.STAYEPILOGUE);\r
1021         this.sysEventHandler.sysEventStayEpilogue(winner, hour, minute);\r
1022 \r
1023         sweepSpace();\r
1024 \r
1025         return true;\r
1026     }\r
1027 \r
1028     private static final Pattern GAMEOVER_PATTERN =\r
1029             compile("終了しました。" + "<br />");\r
1030 \r
1031     /**\r
1032      * GAMEOVERメッセージのパースを試みる。\r
1033      * @return マッチしたらtrue\r
1034      * @throws HtmlParseException パースエラー\r
1035      */\r
1036     private boolean probeGameOver() throws HtmlParseException{\r
1037         pushRegion();\r
1038 \r
1039         sweepSpace();\r
1040 \r
1041         if( ! lookingAtProbe(GAMEOVER_PATTERN)){\r
1042             popRegion();\r
1043             return false;\r
1044         }\r
1045 \r
1046         shrinkRegion();\r
1047 \r
1048         this.sysEventHandler.sysEventType(SysEventType.GAMEOVER);\r
1049 \r
1050         sweepSpace();\r
1051 \r
1052         return true;\r
1053     }\r
1054 \r
1055     /**\r
1056      * Extraメッセージをパースする。\r
1057      * @throws HtmlParseException パースエラー\r
1058      */\r
1059     public void parseExtra() throws HtmlParseException{\r
1060         setContextErrorMessage("Unknown Extra message");\r
1061 \r
1062         this.sysEventHandler.startSysEvent(EventFamily.EXTRA);\r
1063 \r
1064         int regionStart = regionStart();\r
1065         int regionEnd   = regionEnd();\r
1066 \r
1067         boolean result =\r
1068                    probeJudge()\r
1069                 || probeGuard()\r
1070                 || probeCounting2();\r
1071         if( ! result ){\r
1072             throw buildParseException();\r
1073         }\r
1074 \r
1075         getMatcher().region(regionStart, regionEnd);\r
1076         parseContent();\r
1077 \r
1078         lookingAtAffirm(C_DIV_PATTERN);\r
1079         shrinkRegion();\r
1080 \r
1081         this.sysEventHandler.endSysEvent();\r
1082 \r
1083         return;\r
1084     }\r
1085 \r
1086     private static final Pattern JUDGE_DELIM_PATTERN =\r
1087             compile("\u0020は、");\r
1088     private static final Pattern JUDGE_TAIL_PATTERN =\r
1089             compile("\u0020を占った。");\r
1090 \r
1091     /**\r
1092      * JUDGEメッセージのパースを試みる。\r
1093      * @return マッチしたらtrue\r
1094      * @throws HtmlParseException パースエラー\r
1095      */\r
1096     private boolean probeJudge() throws HtmlParseException{\r
1097         SeqRange judgeByRange = this.rangepool_1;\r
1098         SeqRange judgeToRange = this.rangepool_2;\r
1099 \r
1100         pushRegion();\r
1101 \r
1102         sweepSpace();\r
1103 \r
1104         if( ! lookingAtProbe(AVATAR_PATTERN)){\r
1105             popRegion();\r
1106             return false;\r
1107         }\r
1108         judgeByRange.setLastMatchedRange(getMatcher());\r
1109         shrinkRegion();\r
1110 \r
1111         if( ! lookingAtProbe(JUDGE_DELIM_PATTERN)){\r
1112             popRegion();\r
1113             return false;\r
1114         }\r
1115         shrinkRegion();\r
1116 \r
1117         if( ! lookingAtProbe(AVATAR_PATTERN)){\r
1118             popRegion();\r
1119             return false;\r
1120         }\r
1121         judgeToRange.setLastMatchedRange(getMatcher());\r
1122         shrinkRegion();\r
1123 \r
1124         if( ! lookingAtProbe(JUDGE_TAIL_PATTERN)){\r
1125             popRegion();\r
1126             return false;\r
1127         }\r
1128         shrinkRegion();\r
1129 \r
1130         this.sysEventHandler.sysEventType(SysEventType.JUDGE);\r
1131         this.sysEventHandler\r
1132             .sysEventJudge(getContent(),\r
1133                            judgeByRange,\r
1134                            judgeToRange );\r
1135         sweepSpace();\r
1136 \r
1137         return true;\r
1138     }\r
1139 \r
1140     private static final Pattern GUARD_DELIM_PATTERN =\r
1141             compile("\u0020は、");\r
1142     private static final Pattern GUARD_TAIL_PATTERN =\r
1143             compile("\u0020を守っている。");\r
1144 \r
1145     /**\r
1146      * GUARDメッセージのパースを試みる。\r
1147      * @return マッチしたらtrue\r
1148      * @throws HtmlParseException パースエラー\r
1149      */\r
1150     private boolean probeGuard() throws HtmlParseException{\r
1151         SeqRange guardByRange = this.rangepool_1;\r
1152         SeqRange guardToRange = this.rangepool_2;\r
1153 \r
1154         pushRegion();\r
1155 \r
1156         sweepSpace();\r
1157 \r
1158         if( ! lookingAtProbe(AVATAR_PATTERN)){\r
1159             popRegion();\r
1160             return false;\r
1161         }\r
1162         guardByRange.setLastMatchedRange(getMatcher());\r
1163         shrinkRegion();\r
1164 \r
1165         if( ! lookingAtProbe(GUARD_DELIM_PATTERN)){\r
1166             popRegion();\r
1167             return false;\r
1168         }\r
1169         shrinkRegion();\r
1170 \r
1171         if( ! lookingAtProbe(AVATAR_PATTERN)){\r
1172             popRegion();\r
1173             return false;\r
1174         }\r
1175         guardToRange.setLastMatchedRange(getMatcher());\r
1176         shrinkRegion();\r
1177 \r
1178         if( ! lookingAtProbe(GUARD_TAIL_PATTERN)){\r
1179             popRegion();\r
1180             return false;\r
1181         }\r
1182         shrinkRegion();\r
1183 \r
1184         this.sysEventHandler.sysEventType(SysEventType.GUARD);\r
1185         this.sysEventHandler.sysEventGuard(getContent(),\r
1186                                            guardByRange,\r
1187                                            guardToRange );\r
1188         sweepSpace();\r
1189 \r
1190         return true;\r
1191     }\r
1192 \r
1193     private static final Pattern CONTENT_PATTERN =\r
1194             compile(\r
1195              "("\r
1196                 +"[^<>\\n\\r]+"\r
1197             +")|("\r
1198                 +"<br />"\r
1199             +")|(?:"\r
1200                 +"<a\u0020href=\"([^\"]*)\">([^<>]*)</a>"\r
1201             +")"\r
1202             );\r
1203 \r
1204     /**\r
1205      * システムイベントの内容文字列をパースする。\r
1206      * @throws HtmlParseException パースエラー\r
1207      */\r
1208     private void parseContent() throws HtmlParseException{\r
1209         SeqRange anchorRange  = this.rangepool_1;\r
1210         SeqRange contentRange = this.rangepool_2;\r
1211 \r
1212         sweepSpace();\r
1213 \r
1214         for(;;){\r
1215             if( ! lookingAtProbe(CONTENT_PATTERN) ){\r
1216                 break;\r
1217             }\r
1218 \r
1219             if(isGroupMatched(1)){\r
1220                 contentRange.setLastMatchedGroupRange(getMatcher(), 1);\r
1221                 this.sysEventHandler\r
1222                     .sysEventContent(getContent(), contentRange);\r
1223             }else if(isGroupMatched(2)){\r
1224                 this.sysEventHandler.sysEventContentBreak();\r
1225             }else if(isGroupMatched(3)){\r
1226                 anchorRange.setLastMatchedGroupRange(getMatcher(), 3);\r
1227                 contentRange.setLastMatchedGroupRange(getMatcher(), 4);\r
1228                 this.sysEventHandler\r
1229                     .sysEventContentAnchor(getContent(),\r
1230                                            anchorRange,\r
1231                                            contentRange );\r
1232             }\r
1233 \r
1234             shrinkRegion();\r
1235         }\r
1236 \r
1237         sweepSpace();\r
1238 \r
1239         return;\r
1240     }\r
1241 \r
1242     /**\r
1243      * 一時的に現在の検索領域を待避する。\r
1244      * 待避できるのは1回のみ。複数回スタックはできない。\r
1245      * @see #popRegion()\r
1246      */\r
1247     private void pushRegion(){\r
1248         this.pushedRegionStart = regionStart();\r
1249         this.pushedRegionEnd   = regionEnd();\r
1250         return;\r
1251     }\r
1252 \r
1253     /**\r
1254      * 一時的に待避した検索領域を復活させる。\r
1255      * @throws IllegalStateException まだ何も待避していない。\r
1256      * @see #pushRegion()\r
1257      */\r
1258     private void popRegion() throws IllegalStateException{\r
1259         if(this.pushedRegionStart < 0 || this.pushedRegionEnd < 0){\r
1260             throw new IllegalStateException();\r
1261         }\r
1262 \r
1263         if(   this.pushedRegionStart != regionStart()\r
1264            || this.pushedRegionEnd   != regionEnd()  ){\r
1265             getMatcher().region(this.pushedRegionStart, this.pushedRegionEnd);\r
1266         }\r
1267 \r
1268         this.pushedRegionStart = -1;\r
1269         this.pushedRegionEnd   = -1;\r
1270 \r
1271         return;\r
1272     }\r
1273 \r
1274 }\r