OSDN Git Service

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