OSDN Git Service

from subversion repository
[jindolf/Jindolf.git] / src / main / java / jp / sourceforge / jindolf / GameSummary.java
1 /*\r
2  * Summarize game information\r
3  *\r
4  * Copyright(c) 2009 olyutorskii\r
5  * $Id: GameSummary.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.net.MalformedURLException;\r
11 import java.net.URI;\r
12 import java.net.URISyntaxException;\r
13 import java.net.URL;\r
14 import java.text.DateFormat;\r
15 import java.util.Collections;\r
16 import java.util.Comparator;\r
17 import java.util.Date;\r
18 import java.util.EnumMap;\r
19 import java.util.HashMap;\r
20 import java.util.LinkedList;\r
21 import java.util.List;\r
22 import java.util.Map;\r
23 import java.util.Set;\r
24 import jp.sourceforge.jindolf.corelib.Destiny;\r
25 import jp.sourceforge.jindolf.corelib.GameRole;\r
26 import jp.sourceforge.jindolf.corelib.SysEventType;\r
27 import jp.sourceforge.jindolf.corelib.Team;\r
28 import jp.sourceforge.jindolf.corelib.VillageState;\r
29 \r
30 /**\r
31  * 決着の付いたゲームのサマリを集計。\r
32  */\r
33 public class GameSummary{\r
34 \r
35     /** キャスティング表示用Comparator。 */\r
36     public static final Comparator<Player> COMPARATOR_CASTING =\r
37             new CastingComparator();\r
38 \r
39     /**\r
40      * プレイヤーのリストから役職バランス文字列を得る。\r
41      * ex) "村村占霊狂狼"\r
42      * @param players プレイヤーのリスト\r
43      * @return 役職バランス文字列\r
44      */\r
45     public static String getRoleBalanceSequence(List<Player> players){\r
46         List<GameRole> roleList = new LinkedList<GameRole>();\r
47         for(Player player : players){\r
48             GameRole role = player.getRole();\r
49             roleList.add(role);\r
50         }\r
51         Collections.sort(roleList, GameRole.getPowerBalanceComparator());\r
52 \r
53         StringBuilder result = new StringBuilder();\r
54         for(GameRole role : roleList){\r
55             char ch = role.getShortName();\r
56             result.append(ch);\r
57         }\r
58 \r
59         return result.toString();\r
60     }\r
61 \r
62     private final Map<Avatar, Player> playerMap =\r
63             new HashMap<Avatar, Player>();\r
64     private final List<Player> playerList =\r
65             new LinkedList<Player>();\r
66     private final Map<SysEventType, List<SysEvent>> eventMap =\r
67             new EnumMap<SysEventType, List<SysEvent>>(SysEventType.class);\r
68 \r
69     private final Village village;\r
70 \r
71     // 勝者\r
72     private Team winner;\r
73 \r
74     // 占い先集計\r
75     private int ctScryVillage = 0;\r
76     private int ctScryHamster = 0;\r
77     private int ctScryMadman  = 0;\r
78     private int ctScryWolf    = 0;\r
79 \r
80     // 護衛先集計\r
81     private int ctGuardVillage   = 0;\r
82     private int ctGuardHamster   = 0;\r
83     private int ctGuardMadman    = 0;\r
84     private int ctGuardWolf      = 0;\r
85     private int ctGuardVillageGJ = 0;\r
86     private int ctGuardHamsterGJ = 0;\r
87     private int ctGuardMadmanGJ  = 0;\r
88     private int ctGuardFakeGJ    = 0;\r
89 \r
90     // 発言時刻範囲\r
91     private long talk1stTimeMs = -1;\r
92     private long talkLastTimeMs = -1;\r
93 \r
94     /**\r
95      * コンストラクタ。\r
96      * @param village 村\r
97      */\r
98     public GameSummary(Village village){\r
99         super();\r
100 \r
101         VillageState state = village.getState();\r
102         if(   state != VillageState.EPILOGUE\r
103            && state != VillageState.GAMEOVER){\r
104             throw new IllegalStateException();\r
105         }\r
106 \r
107         this.village = village;\r
108 \r
109         summarize();\r
110 \r
111         return;\r
112     }\r
113 \r
114     /**\r
115      * サマライズ処理。\r
116      */\r
117     private void summarize(){\r
118         buildEventMap();\r
119 \r
120         summarizeTime();\r
121         summarizeWinner();\r
122         summarizePlayers();\r
123 \r
124         for(Period period : this.village.getPeriodList()){\r
125             summarizePeriod(period);\r
126         }\r
127 \r
128         summarizeJudge();\r
129         summarizeGuard();\r
130 \r
131         return;\r
132     }\r
133 \r
134     /**\r
135      * SysEventの種別ごとに集計する。\r
136      */\r
137     private void buildEventMap(){\r
138         for(SysEventType type : SysEventType.values()){\r
139             List<SysEvent> eventList = new LinkedList<SysEvent>();\r
140             this.eventMap.put(type, eventList);\r
141         }\r
142 \r
143         for(Period period : this.village.getPeriodList()){\r
144             for(Topic topic : period.getTopicList()){\r
145                 if( ! (topic instanceof SysEvent) ) continue;\r
146                 SysEvent event = (SysEvent) topic;\r
147                 SysEventType type = event.getSysEventType();\r
148                 List<SysEvent> eventList = this.eventMap.get(type);\r
149                 eventList.add(event);\r
150             }\r
151         }\r
152 \r
153         return;\r
154     }\r
155 \r
156     /**\r
157      * 勝者集計。\r
158      */\r
159     private void summarizeWinner(){\r
160         List<SysEvent> eventList;\r
161 \r
162         eventList = this.eventMap.get(SysEventType.WINVILLAGE);\r
163         if( ! eventList.isEmpty() ){\r
164             this.winner = Team.VILLAGE;\r
165         }\r
166 \r
167         eventList = this.eventMap.get(SysEventType.WINWOLF);\r
168         if(  ! eventList.isEmpty() ){\r
169             this.winner = Team.WOLF;\r
170         }\r
171 \r
172         eventList = this.eventMap.get(SysEventType.WINHAMSTER);\r
173         if(  ! eventList.isEmpty() ){\r
174             this.winner = Team.HAMSTER;\r
175         }\r
176 \r
177         if(this.winner == null) assert false;\r
178 \r
179         return;\r
180     }\r
181 \r
182     /**\r
183      * 参加者集計。\r
184      */\r
185     private void summarizePlayers(){\r
186         List<SysEvent> eventList;\r
187 \r
188         List<Avatar>       avatarList;\r
189         List<GameRole>     roleList;\r
190         List<Integer>      integerList;\r
191         List<CharSequence> textList;\r
192 \r
193         eventList = this.eventMap.get(SysEventType.ONSTAGE);\r
194         for(SysEvent event : eventList){\r
195             avatarList  = event.getAvatarList();\r
196             integerList = event.getIntegerList();\r
197             Avatar onstageAvatar = avatarList.get(0);\r
198             Player onstagePlayer = registPlayer(onstageAvatar);\r
199             onstagePlayer.setEntryNo(integerList.get(0));\r
200         }\r
201 \r
202         eventList = this.eventMap.get(SysEventType.PLAYERLIST);\r
203         assert eventList.size() == 1;\r
204         SysEvent event = eventList.get(0);\r
205 \r
206         avatarList  = event.getAvatarList();\r
207         roleList    = event.getRoleList();\r
208         integerList = event.getIntegerList();\r
209         textList    = event.getCharSequenceList();\r
210         int avatarNum = avatarList.size();\r
211         for(int idx = 0; idx < avatarNum; idx++){\r
212             Avatar avatar = avatarList.get(idx);\r
213             GameRole role = roleList.get(idx);\r
214             CharSequence urlText = textList.get(idx * 2);\r
215             CharSequence idName  = textList.get(idx * 2 + 1);\r
216             int liveOrDead = integerList.get(idx);\r
217 \r
218             Player player = registPlayer(avatar);\r
219             player.setRole(role);\r
220             player.setUrlText(urlText.toString());\r
221             player.setIdName(idName.toString());\r
222             if(liveOrDead != 0){        // 生存\r
223                 player.setObitDay(-1);\r
224                 player.setDestiny(Destiny.ALIVE);\r
225             }\r
226 \r
227             this.playerList.add(player);\r
228         }\r
229 \r
230         return;\r
231     }\r
232 \r
233     /**\r
234      * Periodのサマライズ。\r
235      * @param period Period\r
236      */\r
237     private void summarizePeriod(Period period){\r
238         int day = period.getDay();\r
239         for(Topic topic : period.getTopicList()){\r
240             if(topic instanceof SysEvent){\r
241                 SysEvent sysEvent = (SysEvent) topic;\r
242                 summarizeDestiny(day, sysEvent);\r
243             }\r
244         }\r
245 \r
246         return;\r
247     }\r
248 \r
249     /**\r
250      * 各プレイヤー運命のサマライズ。\r
251      * @param day 日\r
252      * @param sysEvent システムイベント\r
253      */\r
254     private void summarizeDestiny(int day, SysEvent sysEvent){\r
255         List<Avatar>  avatarList  = sysEvent.getAvatarList();\r
256         List<Integer> integerList = sysEvent.getIntegerList();\r
257 \r
258         int avatarTotal = avatarList.size();\r
259         Avatar lastAvatar = null;\r
260         if(avatarTotal > 0) lastAvatar = avatarList.get(avatarTotal - 1);\r
261 \r
262         SysEventType eventType = sysEvent.getSysEventType();\r
263         switch(eventType){\r
264         case EXECUTION:  // G国のみ\r
265             if(integerList.get(avatarTotal - 1) > 0) break;  // 処刑無し\r
266             Player executedPl = registPlayer(lastAvatar);\r
267             executedPl.setDestiny(Destiny.EXECUTED);\r
268             executedPl.setObitDay(day);\r
269             break;\r
270         case SUDDENDEATH:\r
271             Avatar suddenDeathAvatar = avatarList.get(0);\r
272             Player suddenDeathPlayer = registPlayer(suddenDeathAvatar);\r
273             suddenDeathPlayer.setDestiny(Destiny.SUDDENDEATH);\r
274             suddenDeathPlayer.setObitDay(day);\r
275             break;\r
276         case COUNTING:  // G国COUNTING2は運命に関係なし\r
277             if(avatarTotal % 2 == 0) break;  // 処刑無し\r
278             Player executedPlayer = registPlayer(lastAvatar);\r
279             executedPlayer.setDestiny(Destiny.EXECUTED);\r
280             executedPlayer.setObitDay(day);\r
281             break;\r
282         case MURDERED:\r
283             for(Avatar avatar : avatarList){\r
284                 Player player = registPlayer(avatar);\r
285                 player.setDestiny(Destiny.EATEN);\r
286                 player.setObitDay(day);\r
287             }\r
288             // TODO E国ハム溶け処理は後回し\r
289             break;\r
290         default:\r
291             break;\r
292         }\r
293 \r
294         return;\r
295     }\r
296 \r
297     /**\r
298      * 会話時刻のサマライズ。\r
299      */\r
300     private void summarizeTime(){\r
301         for(Period period : this.village.getPeriodList()){\r
302             for(Topic topic : period.getTopicList()){\r
303                 if( ! (topic instanceof Talk) ) continue;\r
304                 Talk talk = (Talk) topic;\r
305 \r
306                 long epoch = talk.getTimeFromID();\r
307 \r
308                 if(this.talk1stTimeMs  < 0) this.talk1stTimeMs  = epoch;\r
309                 if(this.talkLastTimeMs < 0) this.talkLastTimeMs = epoch;\r
310 \r
311                 if(epoch < this.talk1stTimeMs ) this.talk1stTimeMs  = epoch;\r
312                 if(epoch > this.talkLastTimeMs) this.talkLastTimeMs = epoch;\r
313             }\r
314         }\r
315 \r
316         return;\r
317     }\r
318 \r
319     /**\r
320      * 占い師の活動を集計する。\r
321      */\r
322     private void summarizeJudge(){\r
323         List<SysEvent> eventList = this.eventMap.get(SysEventType.JUDGE);\r
324 \r
325         for(SysEvent event : eventList){\r
326             List<Avatar> avatarList  = event.getAvatarList();\r
327             Avatar avatar = avatarList.get(1);\r
328             Player seered = getPlayer(avatar);\r
329             GameRole role = seered.getRole();\r
330             switch(role){\r
331             case WOLF:    this.ctScryWolf++;    break;\r
332             case MADMAN:  this.ctScryMadman++;  break;\r
333             case HAMSTER: this.ctScryHamster++; break;\r
334             default:      this.ctScryVillage++; break;\r
335             }\r
336         }\r
337 \r
338         return;\r
339     }\r
340 \r
341     /**\r
342      * 占い師の活動を文字列化する。\r
343      * @return 占い師の活動\r
344      */\r
345     public CharSequence dumpSeerActivity(){\r
346         StringBuilder result = new StringBuilder();\r
347 \r
348         if(this.ctScryVillage > 0){\r
349             result.append("村陣営を");\r
350             result.append(this.ctScryVillage);\r
351             result.append("回");\r
352         }\r
353 \r
354         if(this.ctScryHamster > 0){\r
355             if(result.length() > 0) result.append('、');\r
356             result.append("ハムスターを");\r
357             result.append(this.ctScryHamster);\r
358             result.append("回");\r
359         }\r
360 \r
361         if(this.ctScryMadman > 0){\r
362             if(result.length() > 0) result.append('、');\r
363             result.append("狂人を");\r
364             result.append(this.ctScryMadman);\r
365             result.append("回");\r
366         }\r
367 \r
368         if(this.ctScryWolf > 0){\r
369             if(result.length() > 0) result.append('、');\r
370             result.append("人狼を");\r
371             result.append(this.ctScryWolf);\r
372             result.append("回");\r
373         }\r
374 \r
375         if(result.length() <= 0) result.append("誰も占わなかった。");\r
376         else                     result.append("占った。");\r
377 \r
378         CharSequence seq = WolfBBS.escapeWikiSyntax(result);\r
379 \r
380         return seq;\r
381     }\r
382 \r
383     /**\r
384      * 狩人の活動を集計する。\r
385      */\r
386     private void summarizeGuard(){\r
387         List<SysEvent> eventList;\r
388 \r
389         eventList = this.eventMap.get(SysEventType.GUARD);\r
390         for(SysEvent event : eventList){\r
391             List<Avatar> avatarList = event.getAvatarList();\r
392             Avatar avatar = avatarList.get(1);\r
393             Player guarded = getPlayer(avatar);\r
394             GameRole guardedRole = guarded.getRole();\r
395             switch(guardedRole){\r
396             case WOLF:    this.ctGuardWolf++;    break;\r
397             case MADMAN:  this.ctGuardMadman++;  break;\r
398             case HAMSTER: this.ctGuardHamster++; break;\r
399             default:      this.ctGuardVillage++; break;\r
400             }\r
401         }\r
402 \r
403         for(Period period : this.village.getPeriodList()){\r
404             summarizeGjPeriod(period);\r
405         }\r
406 \r
407         return;\r
408     }\r
409 \r
410     /**\r
411      * 狩人GJの日ごとの集計。\r
412      * @param period 日\r
413      */\r
414     private void summarizeGjPeriod(Period period){\r
415         if(period.getDay() <= 2) return;\r
416 \r
417         boolean hasAssaultTried = period.hasAssaultTried();\r
418         boolean hunterAlive = false;\r
419         int wolfNum = 0;\r
420 \r
421         Set<Avatar> voters = period.getVoterSet();\r
422         for(Avatar avatar : voters){\r
423             Player player = getPlayer(avatar);\r
424             switch(player.getRole()){\r
425             case HUNTER: hunterAlive = true; break;\r
426             case WOLF:   wolfNum++;          break;\r
427             default:                         break;\r
428             }\r
429         }\r
430 \r
431         Avatar executed = period.getExecutedAvatar();\r
432         if(executed != null){\r
433             Player player = getPlayer(executed);\r
434             switch(player.getRole()){\r
435             case HUNTER: hunterAlive = false; break;\r
436             case WOLF:   wolfNum--;           break;\r
437             default:                          break;\r
438             }\r
439         }\r
440 \r
441         if( ! hunterAlive || wolfNum <= 0) return;\r
442 \r
443         SysEvent sysEvent;\r
444 \r
445         sysEvent = period.getTypedSysEvent(SysEventType.NOMURDER);\r
446         if(sysEvent == null) return;\r
447 \r
448         sysEvent = period.getTypedSysEvent(SysEventType.GUARD);\r
449         if(sysEvent == null) return;\r
450 \r
451         if(hasAssaultTried){\r
452             Avatar guarded = sysEvent.getAvatarList().get(1);\r
453             Player guardedPlayer = getPlayer(guarded);\r
454             GameRole guardedRole = guardedPlayer.getRole();\r
455             switch(guardedRole){\r
456             case MADMAN:  this.ctGuardMadmanGJ++;   break;\r
457             case HAMSTER: this.ctGuardHamsterGJ++;  break;\r
458             default:      this.ctGuardVillageGJ++;  break;\r
459             }\r
460         }else{\r
461             this.ctGuardFakeGJ++;   // 偽装GJ\r
462         }\r
463 \r
464         return;\r
465     }\r
466 \r
467     /**\r
468      * 狩人の活動を文字列化する。\r
469      * @return 狩人の活動\r
470      */\r
471     public CharSequence dumpHunterActivity(){\r
472         StringBuilder result = new StringBuilder();\r
473 \r
474         String atLeast;\r
475         if(this.ctGuardFakeGJ > 0) atLeast = "少なくとも";\r
476         else                       atLeast = "";\r
477 \r
478         if(this.ctGuardVillage > 0){\r
479             result.append(atLeast);\r
480             result.append("村陣営を");\r
481             result.append(this.ctGuardVillage);\r
482             result.append("回護衛し");\r
483             if(this.ctGuardVillageGJ > 0){\r
484                 result.append("GJを");\r
485                 result.append(this.ctGuardVillageGJ);\r
486                 result.append("回出した。");\r
487             }else{\r
488                 result.append("た。");\r
489             }\r
490         }\r
491 \r
492         if(this.ctGuardHamster > 0){\r
493             result.append(atLeast);\r
494             result.append("ハムスターを");\r
495             result.append(this.ctGuardHamster);\r
496             result.append("回護衛し");\r
497             if(this.ctGuardHamsterGJ > 0){\r
498                 result.append("GJを");\r
499                 result.append(this.ctGuardHamsterGJ);\r
500                 result.append("回出した。");\r
501             }else{\r
502                 result.append("た。");\r
503             }\r
504         }\r
505 \r
506         if(this.ctGuardMadman > 0){\r
507             result.append(atLeast);\r
508             result.append("狂人を");\r
509             result.append(this.ctGuardMadman);\r
510             result.append("回護衛し");\r
511             if(this.ctGuardMadmanGJ > 0){\r
512                 result.append("GJを");\r
513                 result.append(this.ctGuardMadmanGJ);\r
514                 result.append("回出した。");\r
515             }else{\r
516                 result.append("た。");\r
517             }\r
518         }\r
519 \r
520         if(this.ctGuardWolf > 0){\r
521             result.append(atLeast);\r
522             result.append("人狼を");\r
523             result.append(this.ctGuardWolf);\r
524             result.append("回護衛した。");\r
525         }\r
526 \r
527         if(this.ctGuardFakeGJ > 0){\r
528             result.append("護衛先は不明ながら偽装GJが");\r
529             result.append(this.ctGuardFakeGJ);\r
530             result.append("回あった。");\r
531         }\r
532 \r
533         if(result.length() <= 0) result.append("誰も護衛できなかった");\r
534 \r
535         CharSequence seq = WolfBBS.escapeWikiSyntax(result);\r
536 \r
537         return seq;\r
538     }\r
539 \r
540     /**\r
541      * 処刑概観を文字列化する。\r
542      * @return 文字列化した処刑概観\r
543      */\r
544     public CharSequence dumpExecutionInfo(){\r
545         StringBuilder result = new StringBuilder();\r
546 \r
547         int exeWolf = 0;\r
548         int exeMad = 0;\r
549         int exeVillage = 0;\r
550         for(Player player : this.playerList){\r
551             Destiny destiny = player.getDestiny();\r
552             if(destiny != Destiny.EXECUTED) continue;\r
553             GameRole role = player.getRole();\r
554             switch(role){\r
555             case WOLF:   exeWolf++;    break;\r
556             case MADMAN: exeMad++;     break;\r
557             default:     exeVillage++; break;\r
558             }\r
559         }\r
560 \r
561         if(exeVillage > 0){\r
562             result.append("▼村陣営×").append(exeVillage).append("回");\r
563         }\r
564         if(exeMad > 0){\r
565             if(result.length() > 0) result.append("、");\r
566             result.append("▼狂×").append(exeMad).append("回");\r
567         }\r
568         if(exeWolf > 0){\r
569             if(result.length() > 0) result.append("、");\r
570             result.append("▼狼×").append(exeWolf).append("回");\r
571         }\r
572         if(result.length() <= 0) result.append("なし");\r
573 \r
574         CharSequence seq = WolfBBS.escapeWikiSyntax(result);\r
575 \r
576         return seq;\r
577     }\r
578 \r
579     /**\r
580      * 襲撃概観を文字列化する。\r
581      * @return 文字列化した襲撃概観\r
582      */\r
583     public CharSequence dumpAssaultInfo(){\r
584         StringBuilder result = new StringBuilder();\r
585 \r
586         int eatMad = 0;\r
587         int eatVillage = 0;\r
588         for(Player player : this.playerList){\r
589             if(player.getAvatar() == Avatar.AVATAR_GERD){\r
590                 result.append("▲ゲルト");\r
591                 continue;\r
592             }\r
593             Destiny destiny = player.getDestiny();\r
594             if(destiny != Destiny.EATEN) continue;\r
595             GameRole role = player.getRole();\r
596             switch(role){\r
597             case MADMAN: eatMad++;     break;\r
598             default:     eatVillage++; break;\r
599             }\r
600         }\r
601 \r
602         if(eatVillage > 0){\r
603             if(result.length() > 0) result.append("、");\r
604             result.append("▲村陣営×").append(eatVillage).append("回");\r
605         }\r
606         if(eatMad > 0){\r
607             if(result.length() > 0) result.append("、");\r
608             result.append("▲狂×").append(eatMad).append("回");\r
609         }\r
610 \r
611         if(result.length() <= 0) result.append("襲撃なし");\r
612 \r
613         CharSequence seq = WolfBBS.escapeWikiSyntax(result);\r
614 \r
615         return seq;\r
616     }\r
617 \r
618     /**\r
619      * まとめサイト用投票Boxを生成する。\r
620      * @return 投票BoxのWikiテキスト\r
621      */\r
622     public CharSequence dumpVoteBox(){\r
623         StringBuilder wikiText = new StringBuilder();\r
624 \r
625         for(Player player : getCastingPlayerList()){\r
626             Avatar avatar = player.getAvatar();\r
627             if(avatar == Avatar.AVATAR_GERD) continue;\r
628             GameRole role = player.getRole();\r
629             CharSequence fullName = avatar.getFullName();\r
630             CharSequence roleName = role.getRoleName();\r
631             StringBuilder line = new StringBuilder();\r
632             line.append("[").append(roleName).append("] ").append(fullName);\r
633             if(wikiText.length() > 0) wikiText.append(',');\r
634             wikiText.append(WolfBBS.escapeWikiSyntax(line));\r
635             wikiText.append("[0]");\r
636         }\r
637 \r
638         wikiText.insert(0, "#vote(").append(")\n");\r
639 \r
640         return wikiText;\r
641     }\r
642 \r
643     /**\r
644      * まとめサイト用キャスト表を生成する。\r
645      * @param iconSet 顔アイコンセット\r
646      * @return キャスト表のWikiテキスト\r
647      */\r
648     public CharSequence dumpCastingBoard(FaceIconSet iconSet){\r
649         StringBuilder wikiText = new StringBuilder();\r
650 \r
651         String vName = this.village.getVillageFullName();\r
652         String generator = Jindolf.TITLE + " Ver." + Jindolf.VERSION;\r
653         String author = iconSet.getAuthor() + "氏"\r
654                        +" [ "+iconSet.getUrlText()+" ]";\r
655 \r
656         wikiText.append(WolfBBS.COMMENTLINE);\r
657         wikiText.append("// ↓キャスト表開始\n");\r
658         wikiText.append("//        Village : " + vName + "\n");\r
659         wikiText.append("//        Generator : " + generator + "\n");\r
660         wikiText.append("//        アイコン作者 : " + author + '\n');\r
661         wikiText.append("// ※アイコン画像の著作財産権保持者"\r
662                        +"および画像サーバ運営者から\n");\r
663         wikiText.append("// 新しい意向が示された場合、"\r
664                        +"そちらを最優先で尊重してください。\n");\r
665         wikiText.append(WolfBBS.COMMENTLINE);\r
666 \r
667         wikiText.append("|配役")\r
668                 .append("|参加者")\r
669                 .append("|役職")\r
670                 .append("|運命")\r
671                 .append("|その活躍")\r
672                 .append("|h")\r
673                 .append('\n');\r
674         wikiText.append(WolfBBS.COMMENTLINE);\r
675 \r
676         for(Player player : getCastingPlayerList()){\r
677             Avatar avatar   = player.getAvatar();\r
678             GameRole role   = player.getRole();\r
679             Destiny destiny = player.getDestiny();\r
680             int obitDay     = player.getObitDay();\r
681             String name     = player.getIdName();\r
682             String urlText  = player.getUrlText();\r
683             if(urlText == null) urlText = "";\r
684             urlText = urlText.replace("~", "%7e");\r
685             urlText = urlText.replace(" ", "%20");\r
686             try{\r
687                 URL url = new URL(urlText);\r
688                 URI uri = url.toURI();\r
689                 urlText = uri.toASCIIString();\r
690             }catch(MalformedURLException e){\r
691                 // NOTHING\r
692             }catch(URISyntaxException e){\r
693                 // NOTHING\r
694             }\r
695             // PukiWikiではURL内の&のエスケープは不要?\r
696 \r
697             wikiText.append("// ========== ");\r
698             wikiText.append(name + " acts as [" + avatar.getName() + "]");\r
699             wikiText.append(" ==========\n");\r
700 \r
701             String teamColor =  "BGCOLOR("\r
702                               + WolfBBS.getTeamWikiColor(role)\r
703                               + "):";\r
704 \r
705             String avatarIcon = iconSet.getAvatarIconWiki(avatar);\r
706 \r
707             wikiText.append('|').append(teamColor);\r
708             wikiText.append(avatarIcon).append("&br;");\r
709 \r
710             wikiText.append("[[").append(avatar.getName()).append("]]");\r
711 \r
712             wikiText.append('|').append(teamColor);\r
713             wikiText.append("[[").append(WolfBBS.escapeWikiBracket(name));\r
714             if(urlText != null && urlText.length() > 0){\r
715                 wikiText.append('>').append(urlText);\r
716             }\r
717             wikiText.append("]]");\r
718 \r
719             wikiText.append('|').append(teamColor);\r
720             wikiText.append(WolfBBS.getRoleIconWiki(role));\r
721             wikiText.append("&br;");\r
722             wikiText.append("[[");\r
723             wikiText.append(role.getRoleName());\r
724             wikiText.append("]]");\r
725 \r
726             String destinyColor = WolfBBS.getDestinyColorWiki(destiny);\r
727             wikiText.append('|');\r
728             wikiText.append("BGCOLOR(").append(destinyColor).append("):");\r
729             if(destiny == Destiny.ALIVE){\r
730                 wikiText.append("最後まで&br;生存");\r
731             }else{\r
732                 wikiText.append(obitDay).append("日目").append("&br;");\r
733                 wikiText.append(destiny.getMessage());\r
734             }\r
735 \r
736             wikiText.append('|');\r
737             wikiText.append(avatar.getJobTitle()).append('。');\r
738 \r
739             if(avatar == Avatar.AVATAR_GERD){\r
740                 wikiText.append("寝てばかりいた。");\r
741             }else if(role == GameRole.HUNTER){\r
742                 CharSequence report = dumpHunterActivity();\r
743                 wikiText.append(report);\r
744             }else if(role == GameRole.SEER){\r
745                 CharSequence report = dumpSeerActivity();\r
746                 wikiText.append(report);\r
747             }\r
748 \r
749             wikiText.append("|\n");\r
750 \r
751         }\r
752 \r
753         wikiText.append("|>|>|>|>|");\r
754         wikiText.append("RIGHT:");\r
755         wikiText.append("顔アイコン提供 : [[");\r
756         wikiText.append(WolfBBS.escapeWikiBracket(iconSet.getAuthor()));\r
757         wikiText.append(">" + iconSet.getUrlText());\r
758         wikiText.append("]]氏");\r
759         wikiText.append("|\n");\r
760 \r
761         wikiText.append(WolfBBS.COMMENTLINE);\r
762         wikiText.append("// ↑キャスト表ここまで\n");\r
763         wikiText.append(WolfBBS.COMMENTLINE);\r
764 \r
765         return wikiText;\r
766     }\r
767 \r
768     /**\r
769      * 村詳細情報を出力する。\r
770      * @return 村詳細情報\r
771      */\r
772     public CharSequence dumpVillageWiki(){\r
773         StringBuilder wikiText = new StringBuilder();\r
774 \r
775         DateFormat dform =\r
776                 DateFormat.getDateTimeInstance(DateFormat.FULL,\r
777                                                DateFormat.FULL);\r
778 \r
779         String vName = this.village.getVillageFullName();\r
780         String generator = Jindolf.TITLE + " Ver." + Jindolf.VERSION;\r
781 \r
782         wikiText.append(WolfBBS.COMMENTLINE);\r
783         wikiText.append("// ↓村詳細開始\n");\r
784         wikiText.append("//        Village : " + vName + "\n");\r
785         wikiText.append("//        Generator : " + generator + "\n");\r
786 \r
787         wikiText.append("* 村の詳細\n");\r
788 \r
789         wikiText.append(WolfBBS.COMMENTLINE);\r
790         wikiText.append("- 勝者\n");\r
791         Team winnerTeam = getWinnerTeam();\r
792         String wonTeam = winnerTeam.getTeamName();\r
793         wikiText.append(wonTeam).append('\n');\r
794 \r
795         wikiText.append(WolfBBS.COMMENTLINE);\r
796         wikiText.append("- エントリー開始時刻\n");\r
797         Date date = get1stTalkDate();\r
798         String talk1st = dform.format(date);\r
799         wikiText.append(talk1st).append('\n');\r
800 \r
801         wikiText.append(WolfBBS.COMMENTLINE);\r
802         wikiText.append("- 参加人数\n");\r
803         int avatarNum = countAvatarNum();\r
804         String totalMember = "ゲルト + " + (avatarNum - 1) + "名 = "\r
805                             + avatarNum + "名";\r
806         wikiText.append(WolfBBS.escapeWikiSyntax(totalMember))\r
807                 .append('\n');\r
808 \r
809         wikiText.append(WolfBBS.COMMENTLINE);\r
810         wikiText.append("- 役職内訳\n");\r
811         StringBuilder roleMsg = new StringBuilder();\r
812         for(GameRole role : GameRole.values()){\r
813             List<Player> players = getRoledPlayerList(role);\r
814             String roleName = role.getRoleName();\r
815             if(players.size() <= 0) continue;\r
816             if(roleMsg.length() > 0) roleMsg.append('、');\r
817             roleMsg.append(roleName)\r
818                    .append(" × ")\r
819                    .append(players.size())\r
820                    .append("名");\r
821         }\r
822         wikiText.append(WolfBBS.escapeWikiSyntax(roleMsg)).append('\n');\r
823 \r
824         wikiText.append(WolfBBS.COMMENTLINE);\r
825         wikiText.append("- 処刑内訳\n");\r
826         wikiText.append(dumpExecutionInfo()).append('\n');\r
827 \r
828         wikiText.append(WolfBBS.COMMENTLINE);\r
829         wikiText.append("- 襲撃内訳\n");\r
830         wikiText.append(dumpAssaultInfo()).append('\n');\r
831 \r
832         wikiText.append(WolfBBS.COMMENTLINE);\r
833         wikiText.append("- 突然死\n");\r
834         wikiText.append(countSuddenDeath()).append("名").append('\n');\r
835 \r
836         wikiText.append(WolfBBS.COMMENTLINE);\r
837         wikiText.append("- 人口推移\n");\r
838         for(int day = 1; day < this.village.getPeriodSize(); day++){\r
839             List<Player> players = getSurvivorList(day);\r
840             CharSequence roleSeq =\r
841                     GameSummary.getRoleBalanceSequence(players);\r
842             String daySeq;\r
843             Period period = this.village.getPeriod(day);\r
844             daySeq = period.getCaption();\r
845             wikiText.append('|')\r
846                     .append(daySeq)\r
847                     .append('|')\r
848                     .append(roleSeq)\r
849                     .append("|\n");\r
850         }\r
851 \r
852         wikiText.append(WolfBBS.COMMENTLINE);\r
853         wikiText.append("- 占い師の成績\n");\r
854         wikiText.append(dumpSeerActivity()).append('\n');\r
855 \r
856         wikiText.append(WolfBBS.COMMENTLINE);\r
857         wikiText.append("- 狩人の成績\n");\r
858         wikiText.append(dumpHunterActivity()).append('\n');\r
859 \r
860         wikiText.append(WolfBBS.COMMENTLINE);\r
861         wikiText.append("// ↑村詳細ここまで\n");\r
862         wikiText.append(WolfBBS.COMMENTLINE);\r
863 \r
864         return wikiText;\r
865     }\r
866 \r
867     /**\r
868      * 最初の発言の時刻を得る。\r
869      * @return 時刻\r
870      */\r
871     public Date get1stTalkDate(){\r
872         return new Date(this.talk1stTimeMs);\r
873     }\r
874 \r
875     /**\r
876      * 最後の発言の時刻を得る。\r
877      * @return 時刻\r
878      */\r
879     public Date getLastTalkDate(){\r
880         return new Date(this.talkLastTimeMs);\r
881     }\r
882 \r
883     /**\r
884      * 指定した日の生存者一覧を得る。\r
885      * @param day 日\r
886      * @return 生存者一覧\r
887      */\r
888     public List<Player> getSurvivorList(int day){\r
889         if(day < 0 || this.village.getPeriodSize() <= day){\r
890             throw new IndexOutOfBoundsException();\r
891         }\r
892 \r
893         List<Player> result = new LinkedList<Player>();\r
894 \r
895         Period period = this.village.getPeriod(day);\r
896 \r
897         if(   period.isPrologue()\r
898            || (period.isProgress() && day == 1) ){\r
899             result.addAll(this.playerList);\r
900             return result;\r
901         }\r
902 \r
903         if(period.isEpilogue()){\r
904             for(Player player : this.playerList){\r
905                 if(player.getDestiny() == Destiny.ALIVE){\r
906                     result.add(player);\r
907                 }\r
908             }\r
909             return result;\r
910         }\r
911 \r
912         for(Topic topic : period.getTopicList()){\r
913             if( ! (topic instanceof SysEvent) ) continue;\r
914             SysEvent sysEvent = (SysEvent) topic;\r
915             if(sysEvent.getSysEventType() == SysEventType.SURVIVOR){\r
916                 List<Avatar> avatarList = sysEvent.getAvatarList();\r
917                 for(Avatar avatar : avatarList){\r
918                     Player player = getPlayer(avatar);\r
919                     result.add(player);\r
920                 }\r
921             }\r
922         }\r
923 \r
924         return result;\r
925     }\r
926 \r
927     /**\r
928      * プレイヤー一覧を得る。\r
929      * 参加エントリー順\r
930      * @return プレイヤーのリスト\r
931      */\r
932     public List<Player> getPlayerList(){\r
933         List<Player> result = Collections.unmodifiableList(this.playerList);\r
934         return result;\r
935     }\r
936 \r
937     /**\r
938      * キャスティング表用にソートされたプレイヤー一覧を得る。\r
939      * @return プレイヤーのリスト\r
940      */\r
941     public List<Player> getCastingPlayerList(){\r
942         List<Player> sortedPlayers =\r
943                 new LinkedList<Player>();\r
944         sortedPlayers.addAll(this.playerList);\r
945         Collections.sort(sortedPlayers, COMPARATOR_CASTING);\r
946         return sortedPlayers;\r
947     }\r
948 \r
949     /**\r
950      * 指定された役職のプレイヤー一覧を得る。\r
951      * @param role 役職\r
952      * @return 役職に合致するプレイヤーのリスト\r
953      */\r
954     public List<Player> getRoledPlayerList(GameRole role){\r
955         List<Player> result = new LinkedList<Player>();\r
956 \r
957         for(Player player : this.playerList){\r
958             if(player.getRole() == role){\r
959                 result.add(player);\r
960             }\r
961         }\r
962 \r
963         return result;\r
964     }\r
965 \r
966     /**\r
967      * 勝利陣営を得る。\r
968      * @return 勝利した陣営\r
969      */\r
970     public Team getWinnerTeam(){\r
971         return this.winner;\r
972     }\r
973 \r
974     /**\r
975      * 突然死者数を得る。\r
976      * @return 突然死者数\r
977      */\r
978     public int countSuddenDeath(){\r
979         int suddenDeath = 0;\r
980         for(Player player : this.playerList){\r
981             if(player.getDestiny() == Destiny.SUDDENDEATH) suddenDeath++;\r
982         }\r
983         return suddenDeath;\r
984     }\r
985 \r
986     /**\r
987      * 参加プレイヤー総数を得る。\r
988      * @return プレイヤー総数\r
989      */\r
990     public int countAvatarNum(){\r
991         int playerNum = this.playerList.size();\r
992         return playerNum;\r
993     }\r
994 \r
995     /**\r
996      * AvatarからPlayerを得る。\r
997      * 参加していないAvatarならnullを返す。\r
998      * @param avatar Avatar\r
999      * @return Player\r
1000      */\r
1001     public final Player getPlayer(Avatar avatar){\r
1002         Player player = this.playerMap.get(avatar);\r
1003         return player;\r
1004     }\r
1005 \r
1006     /**\r
1007      * AvatarからPlayerを得る。\r
1008      * 無ければ新規に作る。\r
1009      * @param avatar Avatar\r
1010      * @return Player\r
1011      */\r
1012     private Player registPlayer(Avatar avatar){\r
1013         Player player = getPlayer(avatar);\r
1014         if(player == null){\r
1015             player = new Player();\r
1016             player.setAvatar(avatar);\r
1017             this.playerMap.put(avatar, player);\r
1018         }\r
1019         return player;\r
1020     }\r
1021 \r
1022     /**\r
1023      * プレイヤーのソート仕様の記述。\r
1024      * まとめサイトのキャスト表向け。\r
1025      */\r
1026     private static final class CastingComparator\r
1027             implements Comparator<Player> {\r
1028 \r
1029         /**\r
1030          * コンストラクタ。\r
1031          */\r
1032         private CastingComparator(){\r
1033             super();\r
1034             return;\r
1035         }\r
1036 \r
1037         /**\r
1038          * {@inheritDoc}\r
1039          * @param p1 {@inheritDoc}\r
1040          * @param p2 {@inheritDoc}\r
1041          * @return {@inheritDoc}\r
1042          */\r
1043         public int compare(Player p1, Player p2){\r
1044             if(p1 == p2) return 0;\r
1045             if(p1 == null) return -1;\r
1046             if(p2 == null) return +1;\r
1047 \r
1048             // ゲルトが最前\r
1049             Avatar avatar1 = p1.getAvatar();\r
1050             Avatar avatar2 = p2.getAvatar();\r
1051             if(avatar1.equals(avatar2)) return 0;\r
1052             if(avatar1 == Avatar.AVATAR_GERD) return -1;\r
1053             if(avatar2 == Avatar.AVATAR_GERD) return +1;\r
1054 \r
1055             // 生存者は最後\r
1056             Destiny dest1 = p1.getDestiny();\r
1057             Destiny dest2 = p2.getDestiny();\r
1058             if(dest1 != dest2){\r
1059                 if     (dest1 == Destiny.ALIVE) return +1;\r
1060                 else if(dest2 == Destiny.ALIVE) return -1;\r
1061             }\r
1062 \r
1063             // 退場順\r
1064             int obitDay1 = p1.getObitDay();\r
1065             int obitDay2 = p2.getObitDay();\r
1066             if(obitDay1 > obitDay2) return +1;\r
1067             if(obitDay1 < obitDay2) return -1;\r
1068 \r
1069             // 運命順\r
1070             int destinyOrder = dest1.compareTo(dest2);\r
1071             if(destinyOrder != 0) return destinyOrder;\r
1072 \r
1073             // エントリー順\r
1074             int entryOrder = p1.getEntryNo() - p2.getEntryNo();\r
1075 \r
1076             return entryOrder;\r
1077         }\r
1078     }\r
1079 \r
1080     // TODO E国ハムスター対応\r
1081 }\r