OSDN Git Service

Merge commit '2458eff3aea04f67893bc824b5cf896fbb767332'
[jindolf/Jindolf.git] / src / main / java / jp / sourceforge / jindolf / GameSummary.java
diff --git a/src/main/java/jp/sourceforge/jindolf/GameSummary.java b/src/main/java/jp/sourceforge/jindolf/GameSummary.java
new file mode 100644 (file)
index 0000000..7c54215
--- /dev/null
@@ -0,0 +1,1081 @@
+/*\r
+ * Summarize game information\r
+ *\r
+ * Copyright(c) 2009 olyutorskii\r
+ * $Id: GameSummary.java 1028 2010-05-13 10:15:11Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf;\r
+\r
+import java.net.MalformedURLException;\r
+import java.net.URI;\r
+import java.net.URISyntaxException;\r
+import java.net.URL;\r
+import java.text.DateFormat;\r
+import java.util.Collections;\r
+import java.util.Comparator;\r
+import java.util.Date;\r
+import java.util.EnumMap;\r
+import java.util.HashMap;\r
+import java.util.LinkedList;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Set;\r
+import jp.sourceforge.jindolf.corelib.Destiny;\r
+import jp.sourceforge.jindolf.corelib.GameRole;\r
+import jp.sourceforge.jindolf.corelib.SysEventType;\r
+import jp.sourceforge.jindolf.corelib.Team;\r
+import jp.sourceforge.jindolf.corelib.VillageState;\r
+\r
+/**\r
+ * 決着の付いたゲームのサマリを集計。\r
+ */\r
+public class GameSummary{\r
+\r
+    /** キャスティング表示用Comparator。 */\r
+    public static final Comparator<Player> COMPARATOR_CASTING =\r
+            new CastingComparator();\r
+\r
+    /**\r
+     * プレイヤーのリストから役職バランス文字列を得る。\r
+     * ex) "村村占霊狂狼"\r
+     * @param players プレイヤーのリスト\r
+     * @return 役職バランス文字列\r
+     */\r
+    public static String getRoleBalanceSequence(List<Player> players){\r
+        List<GameRole> roleList = new LinkedList<GameRole>();\r
+        for(Player player : players){\r
+            GameRole role = player.getRole();\r
+            roleList.add(role);\r
+        }\r
+        Collections.sort(roleList, GameRole.getPowerBalanceComparator());\r
+\r
+        StringBuilder result = new StringBuilder();\r
+        for(GameRole role : roleList){\r
+            char ch = role.getShortName();\r
+            result.append(ch);\r
+        }\r
+\r
+        return result.toString();\r
+    }\r
+\r
+    private final Map<Avatar, Player> playerMap =\r
+            new HashMap<Avatar, Player>();\r
+    private final List<Player> playerList =\r
+            new LinkedList<Player>();\r
+    private final Map<SysEventType, List<SysEvent>> eventMap =\r
+            new EnumMap<SysEventType, List<SysEvent>>(SysEventType.class);\r
+\r
+    private final Village village;\r
+\r
+    // 勝者\r
+    private Team winner;\r
+\r
+    // 占い先集計\r
+    private int ctScryVillage = 0;\r
+    private int ctScryHamster = 0;\r
+    private int ctScryMadman  = 0;\r
+    private int ctScryWolf    = 0;\r
+\r
+    // 護衛先集計\r
+    private int ctGuardVillage   = 0;\r
+    private int ctGuardHamster   = 0;\r
+    private int ctGuardMadman    = 0;\r
+    private int ctGuardWolf      = 0;\r
+    private int ctGuardVillageGJ = 0;\r
+    private int ctGuardHamsterGJ = 0;\r
+    private int ctGuardMadmanGJ  = 0;\r
+    private int ctGuardFakeGJ    = 0;\r
+\r
+    // 発言時刻範囲\r
+    private long talk1stTimeMs = -1;\r
+    private long talkLastTimeMs = -1;\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     * @param village 村\r
+     */\r
+    public GameSummary(Village village){\r
+        super();\r
+\r
+        VillageState state = village.getState();\r
+        if(   state != VillageState.EPILOGUE\r
+           && state != VillageState.GAMEOVER){\r
+            throw new IllegalStateException();\r
+        }\r
+\r
+        this.village = village;\r
+\r
+        summarize();\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * サマライズ処理。\r
+     */\r
+    private void summarize(){\r
+        buildEventMap();\r
+\r
+        summarizeTime();\r
+        summarizeWinner();\r
+        summarizePlayers();\r
+\r
+        for(Period period : this.village.getPeriodList()){\r
+            summarizePeriod(period);\r
+        }\r
+\r
+        summarizeJudge();\r
+        summarizeGuard();\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * SysEventの種別ごとに集計する。\r
+     */\r
+    private void buildEventMap(){\r
+        for(SysEventType type : SysEventType.values()){\r
+            List<SysEvent> eventList = new LinkedList<SysEvent>();\r
+            this.eventMap.put(type, eventList);\r
+        }\r
+\r
+        for(Period period : this.village.getPeriodList()){\r
+            for(Topic topic : period.getTopicList()){\r
+                if( ! (topic instanceof SysEvent) ) continue;\r
+                SysEvent event = (SysEvent) topic;\r
+                SysEventType type = event.getSysEventType();\r
+                List<SysEvent> eventList = this.eventMap.get(type);\r
+                eventList.add(event);\r
+            }\r
+        }\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 勝者集計。\r
+     */\r
+    private void summarizeWinner(){\r
+        List<SysEvent> eventList;\r
+\r
+        eventList = this.eventMap.get(SysEventType.WINVILLAGE);\r
+        if( ! eventList.isEmpty() ){\r
+            this.winner = Team.VILLAGE;\r
+        }\r
+\r
+        eventList = this.eventMap.get(SysEventType.WINWOLF);\r
+        if(  ! eventList.isEmpty() ){\r
+            this.winner = Team.WOLF;\r
+        }\r
+\r
+        eventList = this.eventMap.get(SysEventType.WINHAMSTER);\r
+        if(  ! eventList.isEmpty() ){\r
+            this.winner = Team.HAMSTER;\r
+        }\r
+\r
+        if(this.winner == null) assert false;\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 参加者集計。\r
+     */\r
+    private void summarizePlayers(){\r
+        List<SysEvent> eventList;\r
+\r
+        List<Avatar>       avatarList;\r
+        List<GameRole>     roleList;\r
+        List<Integer>      integerList;\r
+        List<CharSequence> textList;\r
+\r
+        eventList = this.eventMap.get(SysEventType.ONSTAGE);\r
+        for(SysEvent event : eventList){\r
+            avatarList  = event.getAvatarList();\r
+            integerList = event.getIntegerList();\r
+            Avatar onstageAvatar = avatarList.get(0);\r
+            Player onstagePlayer = registPlayer(onstageAvatar);\r
+            onstagePlayer.setEntryNo(integerList.get(0));\r
+        }\r
+\r
+        eventList = this.eventMap.get(SysEventType.PLAYERLIST);\r
+        assert eventList.size() == 1;\r
+        SysEvent event = eventList.get(0);\r
+\r
+        avatarList  = event.getAvatarList();\r
+        roleList    = event.getRoleList();\r
+        integerList = event.getIntegerList();\r
+        textList    = event.getCharSequenceList();\r
+        int avatarNum = avatarList.size();\r
+        for(int idx = 0; idx < avatarNum; idx++){\r
+            Avatar avatar = avatarList.get(idx);\r
+            GameRole role = roleList.get(idx);\r
+            CharSequence urlText = textList.get(idx * 2);\r
+            CharSequence idName  = textList.get(idx * 2 + 1);\r
+            int liveOrDead = integerList.get(idx);\r
+\r
+            Player player = registPlayer(avatar);\r
+            player.setRole(role);\r
+            player.setUrlText(urlText.toString());\r
+            player.setIdName(idName.toString());\r
+            if(liveOrDead != 0){        // 生存\r
+                player.setObitDay(-1);\r
+                player.setDestiny(Destiny.ALIVE);\r
+            }\r
+\r
+            this.playerList.add(player);\r
+        }\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * Periodのサマライズ。\r
+     * @param period Period\r
+     */\r
+    private void summarizePeriod(Period period){\r
+        int day = period.getDay();\r
+        for(Topic topic : period.getTopicList()){\r
+            if(topic instanceof SysEvent){\r
+                SysEvent sysEvent = (SysEvent) topic;\r
+                summarizeDestiny(day, sysEvent);\r
+            }\r
+        }\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 各プレイヤー運命のサマライズ。\r
+     * @param day 日\r
+     * @param sysEvent システムイベント\r
+     */\r
+    private void summarizeDestiny(int day, SysEvent sysEvent){\r
+        List<Avatar>  avatarList  = sysEvent.getAvatarList();\r
+        List<Integer> integerList = sysEvent.getIntegerList();\r
+\r
+        int avatarTotal = avatarList.size();\r
+        Avatar lastAvatar = null;\r
+        if(avatarTotal > 0) lastAvatar = avatarList.get(avatarTotal - 1);\r
+\r
+        SysEventType eventType = sysEvent.getSysEventType();\r
+        switch(eventType){\r
+        case EXECUTION:  // G国のみ\r
+            if(integerList.get(avatarTotal - 1) > 0) break;  // 処刑無し\r
+            Player executedPl = registPlayer(lastAvatar);\r
+            executedPl.setDestiny(Destiny.EXECUTED);\r
+            executedPl.setObitDay(day);\r
+            break;\r
+        case SUDDENDEATH:\r
+            Avatar suddenDeathAvatar = avatarList.get(0);\r
+            Player suddenDeathPlayer = registPlayer(suddenDeathAvatar);\r
+            suddenDeathPlayer.setDestiny(Destiny.SUDDENDEATH);\r
+            suddenDeathPlayer.setObitDay(day);\r
+            break;\r
+        case COUNTING:  // G国COUNTING2は運命に関係なし\r
+            if(avatarTotal % 2 == 0) break;  // 処刑無し\r
+            Player executedPlayer = registPlayer(lastAvatar);\r
+            executedPlayer.setDestiny(Destiny.EXECUTED);\r
+            executedPlayer.setObitDay(day);\r
+            break;\r
+        case MURDERED:\r
+            for(Avatar avatar : avatarList){\r
+                Player player = registPlayer(avatar);\r
+                player.setDestiny(Destiny.EATEN);\r
+                player.setObitDay(day);\r
+            }\r
+            // TODO E国ハム溶け処理は後回し\r
+            break;\r
+        default:\r
+            break;\r
+        }\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 会話時刻のサマライズ。\r
+     */\r
+    private void summarizeTime(){\r
+        for(Period period : this.village.getPeriodList()){\r
+            for(Topic topic : period.getTopicList()){\r
+                if( ! (topic instanceof Talk) ) continue;\r
+                Talk talk = (Talk) topic;\r
+\r
+                long epoch = talk.getTimeFromID();\r
+\r
+                if(this.talk1stTimeMs  < 0) this.talk1stTimeMs  = epoch;\r
+                if(this.talkLastTimeMs < 0) this.talkLastTimeMs = epoch;\r
+\r
+                if(epoch < this.talk1stTimeMs ) this.talk1stTimeMs  = epoch;\r
+                if(epoch > this.talkLastTimeMs) this.talkLastTimeMs = epoch;\r
+            }\r
+        }\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 占い師の活動を集計する。\r
+     */\r
+    private void summarizeJudge(){\r
+        List<SysEvent> eventList = this.eventMap.get(SysEventType.JUDGE);\r
+\r
+        for(SysEvent event : eventList){\r
+            List<Avatar> avatarList  = event.getAvatarList();\r
+            Avatar avatar = avatarList.get(1);\r
+            Player seered = getPlayer(avatar);\r
+            GameRole role = seered.getRole();\r
+            switch(role){\r
+            case WOLF:    this.ctScryWolf++;    break;\r
+            case MADMAN:  this.ctScryMadman++;  break;\r
+            case HAMSTER: this.ctScryHamster++; break;\r
+            default:      this.ctScryVillage++; break;\r
+            }\r
+        }\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 占い師の活動を文字列化する。\r
+     * @return 占い師の活動\r
+     */\r
+    public CharSequence dumpSeerActivity(){\r
+        StringBuilder result = new StringBuilder();\r
+\r
+        if(this.ctScryVillage > 0){\r
+            result.append("村陣営を");\r
+            result.append(this.ctScryVillage);\r
+            result.append("回");\r
+        }\r
+\r
+        if(this.ctScryHamster > 0){\r
+            if(result.length() > 0) result.append('、');\r
+            result.append("ハムスターを");\r
+            result.append(this.ctScryHamster);\r
+            result.append("回");\r
+        }\r
+\r
+        if(this.ctScryMadman > 0){\r
+            if(result.length() > 0) result.append('、');\r
+            result.append("狂人を");\r
+            result.append(this.ctScryMadman);\r
+            result.append("回");\r
+        }\r
+\r
+        if(this.ctScryWolf > 0){\r
+            if(result.length() > 0) result.append('、');\r
+            result.append("人狼を");\r
+            result.append(this.ctScryWolf);\r
+            result.append("回");\r
+        }\r
+\r
+        if(result.length() <= 0) result.append("誰も占わなかった。");\r
+        else                     result.append("占った。");\r
+\r
+        CharSequence seq = WolfBBS.escapeWikiSyntax(result);\r
+\r
+        return seq;\r
+    }\r
+\r
+    /**\r
+     * 狩人の活動を集計する。\r
+     */\r
+    private void summarizeGuard(){\r
+        List<SysEvent> eventList;\r
+\r
+        eventList = this.eventMap.get(SysEventType.GUARD);\r
+        for(SysEvent event : eventList){\r
+            List<Avatar> avatarList = event.getAvatarList();\r
+            Avatar avatar = avatarList.get(1);\r
+            Player guarded = getPlayer(avatar);\r
+            GameRole guardedRole = guarded.getRole();\r
+            switch(guardedRole){\r
+            case WOLF:    this.ctGuardWolf++;    break;\r
+            case MADMAN:  this.ctGuardMadman++;  break;\r
+            case HAMSTER: this.ctGuardHamster++; break;\r
+            default:      this.ctGuardVillage++; break;\r
+            }\r
+        }\r
+\r
+        for(Period period : this.village.getPeriodList()){\r
+            summarizeGjPeriod(period);\r
+        }\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 狩人GJの日ごとの集計。\r
+     * @param period 日\r
+     */\r
+    private void summarizeGjPeriod(Period period){\r
+        if(period.getDay() <= 2) return;\r
+\r
+        boolean hasAssaultTried = period.hasAssaultTried();\r
+        boolean hunterAlive = false;\r
+        int wolfNum = 0;\r
+\r
+        Set<Avatar> voters = period.getVoterSet();\r
+        for(Avatar avatar : voters){\r
+            Player player = getPlayer(avatar);\r
+            switch(player.getRole()){\r
+            case HUNTER: hunterAlive = true; break;\r
+            case WOLF:   wolfNum++;          break;\r
+            default:                         break;\r
+            }\r
+        }\r
+\r
+        Avatar executed = period.getExecutedAvatar();\r
+        if(executed != null){\r
+            Player player = getPlayer(executed);\r
+            switch(player.getRole()){\r
+            case HUNTER: hunterAlive = false; break;\r
+            case WOLF:   wolfNum--;           break;\r
+            default:                          break;\r
+            }\r
+        }\r
+\r
+        if( ! hunterAlive || wolfNum <= 0) return;\r
+\r
+        SysEvent sysEvent;\r
+\r
+        sysEvent = period.getTypedSysEvent(SysEventType.NOMURDER);\r
+        if(sysEvent == null) return;\r
+\r
+        sysEvent = period.getTypedSysEvent(SysEventType.GUARD);\r
+        if(sysEvent == null) return;\r
+\r
+        if(hasAssaultTried){\r
+            Avatar guarded = sysEvent.getAvatarList().get(1);\r
+            Player guardedPlayer = getPlayer(guarded);\r
+            GameRole guardedRole = guardedPlayer.getRole();\r
+            switch(guardedRole){\r
+            case MADMAN:  this.ctGuardMadmanGJ++;   break;\r
+            case HAMSTER: this.ctGuardHamsterGJ++;  break;\r
+            default:      this.ctGuardVillageGJ++;  break;\r
+            }\r
+        }else{\r
+            this.ctGuardFakeGJ++;   // 偽装GJ\r
+        }\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 狩人の活動を文字列化する。\r
+     * @return 狩人の活動\r
+     */\r
+    public CharSequence dumpHunterActivity(){\r
+        StringBuilder result = new StringBuilder();\r
+\r
+        String atLeast;\r
+        if(this.ctGuardFakeGJ > 0) atLeast = "少なくとも";\r
+        else                       atLeast = "";\r
+\r
+        if(this.ctGuardVillage > 0){\r
+            result.append(atLeast);\r
+            result.append("村陣営を");\r
+            result.append(this.ctGuardVillage);\r
+            result.append("回護衛し");\r
+            if(this.ctGuardVillageGJ > 0){\r
+                result.append("GJを");\r
+                result.append(this.ctGuardVillageGJ);\r
+                result.append("回出した。");\r
+            }else{\r
+                result.append("た。");\r
+            }\r
+        }\r
+\r
+        if(this.ctGuardHamster > 0){\r
+            result.append(atLeast);\r
+            result.append("ハムスターを");\r
+            result.append(this.ctGuardHamster);\r
+            result.append("回護衛し");\r
+            if(this.ctGuardHamsterGJ > 0){\r
+                result.append("GJを");\r
+                result.append(this.ctGuardHamsterGJ);\r
+                result.append("回出した。");\r
+            }else{\r
+                result.append("た。");\r
+            }\r
+        }\r
+\r
+        if(this.ctGuardMadman > 0){\r
+            result.append(atLeast);\r
+            result.append("狂人を");\r
+            result.append(this.ctGuardMadman);\r
+            result.append("回護衛し");\r
+            if(this.ctGuardMadmanGJ > 0){\r
+                result.append("GJを");\r
+                result.append(this.ctGuardMadmanGJ);\r
+                result.append("回出した。");\r
+            }else{\r
+                result.append("た。");\r
+            }\r
+        }\r
+\r
+        if(this.ctGuardWolf > 0){\r
+            result.append(atLeast);\r
+            result.append("人狼を");\r
+            result.append(this.ctGuardWolf);\r
+            result.append("回護衛した。");\r
+        }\r
+\r
+        if(this.ctGuardFakeGJ > 0){\r
+            result.append("護衛先は不明ながら偽装GJが");\r
+            result.append(this.ctGuardFakeGJ);\r
+            result.append("回あった。");\r
+        }\r
+\r
+        if(result.length() <= 0) result.append("誰も護衛できなかった");\r
+\r
+        CharSequence seq = WolfBBS.escapeWikiSyntax(result);\r
+\r
+        return seq;\r
+    }\r
+\r
+    /**\r
+     * 処刑概観を文字列化する。\r
+     * @return 文字列化した処刑概観\r
+     */\r
+    public CharSequence dumpExecutionInfo(){\r
+        StringBuilder result = new StringBuilder();\r
+\r
+        int exeWolf = 0;\r
+        int exeMad = 0;\r
+        int exeVillage = 0;\r
+        for(Player player : this.playerList){\r
+            Destiny destiny = player.getDestiny();\r
+            if(destiny != Destiny.EXECUTED) continue;\r
+            GameRole role = player.getRole();\r
+            switch(role){\r
+            case WOLF:   exeWolf++;    break;\r
+            case MADMAN: exeMad++;     break;\r
+            default:     exeVillage++; break;\r
+            }\r
+        }\r
+\r
+        if(exeVillage > 0){\r
+            result.append("▼村陣営×").append(exeVillage).append("回");\r
+        }\r
+        if(exeMad > 0){\r
+            if(result.length() > 0) result.append("、");\r
+            result.append("▼狂×").append(exeMad).append("回");\r
+        }\r
+        if(exeWolf > 0){\r
+            if(result.length() > 0) result.append("、");\r
+            result.append("▼狼×").append(exeWolf).append("回");\r
+        }\r
+        if(result.length() <= 0) result.append("なし");\r
+\r
+        CharSequence seq = WolfBBS.escapeWikiSyntax(result);\r
+\r
+        return seq;\r
+    }\r
+\r
+    /**\r
+     * 襲撃概観を文字列化する。\r
+     * @return 文字列化した襲撃概観\r
+     */\r
+    public CharSequence dumpAssaultInfo(){\r
+        StringBuilder result = new StringBuilder();\r
+\r
+        int eatMad = 0;\r
+        int eatVillage = 0;\r
+        for(Player player : this.playerList){\r
+            if(player.getAvatar() == Avatar.AVATAR_GERD){\r
+                result.append("▲ゲルト");\r
+                continue;\r
+            }\r
+            Destiny destiny = player.getDestiny();\r
+            if(destiny != Destiny.EATEN) continue;\r
+            GameRole role = player.getRole();\r
+            switch(role){\r
+            case MADMAN: eatMad++;     break;\r
+            default:     eatVillage++; break;\r
+            }\r
+        }\r
+\r
+        if(eatVillage > 0){\r
+            if(result.length() > 0) result.append("、");\r
+            result.append("▲村陣営×").append(eatVillage).append("回");\r
+        }\r
+        if(eatMad > 0){\r
+            if(result.length() > 0) result.append("、");\r
+            result.append("▲狂×").append(eatMad).append("回");\r
+        }\r
+\r
+        if(result.length() <= 0) result.append("襲撃なし");\r
+\r
+        CharSequence seq = WolfBBS.escapeWikiSyntax(result);\r
+\r
+        return seq;\r
+    }\r
+\r
+    /**\r
+     * まとめサイト用投票Boxを生成する。\r
+     * @return 投票BoxのWikiテキスト\r
+     */\r
+    public CharSequence dumpVoteBox(){\r
+        StringBuilder wikiText = new StringBuilder();\r
+\r
+        for(Player player : getCastingPlayerList()){\r
+            Avatar avatar = player.getAvatar();\r
+            if(avatar == Avatar.AVATAR_GERD) continue;\r
+            GameRole role = player.getRole();\r
+            CharSequence fullName = avatar.getFullName();\r
+            CharSequence roleName = role.getRoleName();\r
+            StringBuilder line = new StringBuilder();\r
+            line.append("[").append(roleName).append("] ").append(fullName);\r
+            if(wikiText.length() > 0) wikiText.append(',');\r
+            wikiText.append(WolfBBS.escapeWikiSyntax(line));\r
+            wikiText.append("[0]");\r
+        }\r
+\r
+        wikiText.insert(0, "#vote(").append(")\n");\r
+\r
+        return wikiText;\r
+    }\r
+\r
+    /**\r
+     * まとめサイト用キャスト表を生成する。\r
+     * @param iconSet 顔アイコンセット\r
+     * @return キャスト表のWikiテキスト\r
+     */\r
+    public CharSequence dumpCastingBoard(FaceIconSet iconSet){\r
+        StringBuilder wikiText = new StringBuilder();\r
+\r
+        String vName = this.village.getVillageFullName();\r
+        String generator = Jindolf.TITLE + " Ver." + Jindolf.VERSION;\r
+        String author = iconSet.getAuthor() + "氏"\r
+                       +" [ "+iconSet.getUrlText()+" ]";\r
+\r
+        wikiText.append(WolfBBS.COMMENTLINE);\r
+        wikiText.append("// ↓キャスト表開始\n");\r
+        wikiText.append("//        Village : " + vName + "\n");\r
+        wikiText.append("//        Generator : " + generator + "\n");\r
+        wikiText.append("//        アイコン作者 : " + author + '\n');\r
+        wikiText.append("// ※アイコン画像の著作財産権保持者"\r
+                       +"および画像サーバ運営者から\n");\r
+        wikiText.append("// 新しい意向が示された場合、"\r
+                       +"そちらを最優先で尊重してください。\n");\r
+        wikiText.append(WolfBBS.COMMENTLINE);\r
+\r
+        wikiText.append("|配役")\r
+                .append("|参加者")\r
+                .append("|役職")\r
+                .append("|運命")\r
+                .append("|その活躍")\r
+                .append("|h")\r
+                .append('\n');\r
+        wikiText.append(WolfBBS.COMMENTLINE);\r
+\r
+        for(Player player : getCastingPlayerList()){\r
+            Avatar avatar   = player.getAvatar();\r
+            GameRole role   = player.getRole();\r
+            Destiny destiny = player.getDestiny();\r
+            int obitDay     = player.getObitDay();\r
+            String name     = player.getIdName();\r
+            String urlText  = player.getUrlText();\r
+            if(urlText == null) urlText = "";\r
+            urlText = urlText.replace("~", "%7e");\r
+            urlText = urlText.replace(" ", "%20");\r
+            try{\r
+                URL url = new URL(urlText);\r
+                URI uri = url.toURI();\r
+                urlText = uri.toASCIIString();\r
+            }catch(MalformedURLException e){\r
+                // NOTHING\r
+            }catch(URISyntaxException e){\r
+                // NOTHING\r
+            }\r
+            // PukiWikiではURL内の&のエスケープは不要?\r
+\r
+            wikiText.append("// ========== ");\r
+            wikiText.append(name + " acts as [" + avatar.getName() + "]");\r
+            wikiText.append(" ==========\n");\r
+\r
+            String teamColor =  "BGCOLOR("\r
+                              + WolfBBS.getTeamWikiColor(role)\r
+                              + "):";\r
+\r
+            String avatarIcon = iconSet.getAvatarIconWiki(avatar);\r
+\r
+            wikiText.append('|').append(teamColor);\r
+            wikiText.append(avatarIcon).append("&br;");\r
+\r
+            wikiText.append("[[").append(avatar.getName()).append("]]");\r
+\r
+            wikiText.append('|').append(teamColor);\r
+            wikiText.append("[[").append(WolfBBS.escapeWikiBracket(name));\r
+            if(urlText != null && urlText.length() > 0){\r
+                wikiText.append('>').append(urlText);\r
+            }\r
+            wikiText.append("]]");\r
+\r
+            wikiText.append('|').append(teamColor);\r
+            wikiText.append(WolfBBS.getRoleIconWiki(role));\r
+            wikiText.append("&br;");\r
+            wikiText.append("[[");\r
+            wikiText.append(role.getRoleName());\r
+            wikiText.append("]]");\r
+\r
+            String destinyColor = WolfBBS.getDestinyColorWiki(destiny);\r
+            wikiText.append('|');\r
+            wikiText.append("BGCOLOR(").append(destinyColor).append("):");\r
+            if(destiny == Destiny.ALIVE){\r
+                wikiText.append("最後まで&br;生存");\r
+            }else{\r
+                wikiText.append(obitDay).append("日目").append("&br;");\r
+                wikiText.append(destiny.getMessage());\r
+            }\r
+\r
+            wikiText.append('|');\r
+            wikiText.append(avatar.getJobTitle()).append('。');\r
+\r
+            if(avatar == Avatar.AVATAR_GERD){\r
+                wikiText.append("寝てばかりいた。");\r
+            }else if(role == GameRole.HUNTER){\r
+                CharSequence report = dumpHunterActivity();\r
+                wikiText.append(report);\r
+            }else if(role == GameRole.SEER){\r
+                CharSequence report = dumpSeerActivity();\r
+                wikiText.append(report);\r
+            }\r
+\r
+            wikiText.append("|\n");\r
+\r
+        }\r
+\r
+        wikiText.append("|>|>|>|>|");\r
+        wikiText.append("RIGHT:");\r
+        wikiText.append("顔アイコン提供 : [[");\r
+        wikiText.append(WolfBBS.escapeWikiBracket(iconSet.getAuthor()));\r
+        wikiText.append(">" + iconSet.getUrlText());\r
+        wikiText.append("]]氏");\r
+        wikiText.append("|\n");\r
+\r
+        wikiText.append(WolfBBS.COMMENTLINE);\r
+        wikiText.append("// ↑キャスト表ここまで\n");\r
+        wikiText.append(WolfBBS.COMMENTLINE);\r
+\r
+        return wikiText;\r
+    }\r
+\r
+    /**\r
+     * 村詳細情報を出力する。\r
+     * @return 村詳細情報\r
+     */\r
+    public CharSequence dumpVillageWiki(){\r
+        StringBuilder wikiText = new StringBuilder();\r
+\r
+        DateFormat dform =\r
+                DateFormat.getDateTimeInstance(DateFormat.FULL,\r
+                                               DateFormat.FULL);\r
+\r
+        String vName = this.village.getVillageFullName();\r
+        String generator = Jindolf.TITLE + " Ver." + Jindolf.VERSION;\r
+\r
+        wikiText.append(WolfBBS.COMMENTLINE);\r
+        wikiText.append("// ↓村詳細開始\n");\r
+        wikiText.append("//        Village : " + vName + "\n");\r
+        wikiText.append("//        Generator : " + generator + "\n");\r
+\r
+        wikiText.append("* 村の詳細\n");\r
+\r
+        wikiText.append(WolfBBS.COMMENTLINE);\r
+        wikiText.append("- 勝者\n");\r
+        Team winnerTeam = getWinnerTeam();\r
+        String wonTeam = winnerTeam.getTeamName();\r
+        wikiText.append(wonTeam).append('\n');\r
+\r
+        wikiText.append(WolfBBS.COMMENTLINE);\r
+        wikiText.append("- エントリー開始時刻\n");\r
+        Date date = get1stTalkDate();\r
+        String talk1st = dform.format(date);\r
+        wikiText.append(talk1st).append('\n');\r
+\r
+        wikiText.append(WolfBBS.COMMENTLINE);\r
+        wikiText.append("- 参加人数\n");\r
+        int avatarNum = countAvatarNum();\r
+        String totalMember = "ゲルト + " + (avatarNum - 1) + "名 = "\r
+                            + avatarNum + "名";\r
+        wikiText.append(WolfBBS.escapeWikiSyntax(totalMember))\r
+                .append('\n');\r
+\r
+        wikiText.append(WolfBBS.COMMENTLINE);\r
+        wikiText.append("- 役職内訳\n");\r
+        StringBuilder roleMsg = new StringBuilder();\r
+        for(GameRole role : GameRole.values()){\r
+            List<Player> players = getRoledPlayerList(role);\r
+            String roleName = role.getRoleName();\r
+            if(players.size() <= 0) continue;\r
+            if(roleMsg.length() > 0) roleMsg.append('、');\r
+            roleMsg.append(roleName)\r
+                   .append(" × ")\r
+                   .append(players.size())\r
+                   .append("名");\r
+        }\r
+        wikiText.append(WolfBBS.escapeWikiSyntax(roleMsg)).append('\n');\r
+\r
+        wikiText.append(WolfBBS.COMMENTLINE);\r
+        wikiText.append("- 処刑内訳\n");\r
+        wikiText.append(dumpExecutionInfo()).append('\n');\r
+\r
+        wikiText.append(WolfBBS.COMMENTLINE);\r
+        wikiText.append("- 襲撃内訳\n");\r
+        wikiText.append(dumpAssaultInfo()).append('\n');\r
+\r
+        wikiText.append(WolfBBS.COMMENTLINE);\r
+        wikiText.append("- 突然死\n");\r
+        wikiText.append(countSuddenDeath()).append("名").append('\n');\r
+\r
+        wikiText.append(WolfBBS.COMMENTLINE);\r
+        wikiText.append("- 人口推移\n");\r
+        for(int day = 1; day < this.village.getPeriodSize(); day++){\r
+            List<Player> players = getSurvivorList(day);\r
+            CharSequence roleSeq =\r
+                    GameSummary.getRoleBalanceSequence(players);\r
+            String daySeq;\r
+            Period period = this.village.getPeriod(day);\r
+            daySeq = period.getCaption();\r
+            wikiText.append('|')\r
+                    .append(daySeq)\r
+                    .append('|')\r
+                    .append(roleSeq)\r
+                    .append("|\n");\r
+        }\r
+\r
+        wikiText.append(WolfBBS.COMMENTLINE);\r
+        wikiText.append("- 占い師の成績\n");\r
+        wikiText.append(dumpSeerActivity()).append('\n');\r
+\r
+        wikiText.append(WolfBBS.COMMENTLINE);\r
+        wikiText.append("- 狩人の成績\n");\r
+        wikiText.append(dumpHunterActivity()).append('\n');\r
+\r
+        wikiText.append(WolfBBS.COMMENTLINE);\r
+        wikiText.append("// ↑村詳細ここまで\n");\r
+        wikiText.append(WolfBBS.COMMENTLINE);\r
+\r
+        return wikiText;\r
+    }\r
+\r
+    /**\r
+     * 最初の発言の時刻を得る。\r
+     * @return 時刻\r
+     */\r
+    public Date get1stTalkDate(){\r
+        return new Date(this.talk1stTimeMs);\r
+    }\r
+\r
+    /**\r
+     * 最後の発言の時刻を得る。\r
+     * @return 時刻\r
+     */\r
+    public Date getLastTalkDate(){\r
+        return new Date(this.talkLastTimeMs);\r
+    }\r
+\r
+    /**\r
+     * 指定した日の生存者一覧を得る。\r
+     * @param day 日\r
+     * @return 生存者一覧\r
+     */\r
+    public List<Player> getSurvivorList(int day){\r
+        if(day < 0 || this.village.getPeriodSize() <= day){\r
+            throw new IndexOutOfBoundsException();\r
+        }\r
+\r
+        List<Player> result = new LinkedList<Player>();\r
+\r
+        Period period = this.village.getPeriod(day);\r
+\r
+        if(   period.isPrologue()\r
+           || (period.isProgress() && day == 1) ){\r
+            result.addAll(this.playerList);\r
+            return result;\r
+        }\r
+\r
+        if(period.isEpilogue()){\r
+            for(Player player : this.playerList){\r
+                if(player.getDestiny() == Destiny.ALIVE){\r
+                    result.add(player);\r
+                }\r
+            }\r
+            return result;\r
+        }\r
+\r
+        for(Topic topic : period.getTopicList()){\r
+            if( ! (topic instanceof SysEvent) ) continue;\r
+            SysEvent sysEvent = (SysEvent) topic;\r
+            if(sysEvent.getSysEventType() == SysEventType.SURVIVOR){\r
+                List<Avatar> avatarList = sysEvent.getAvatarList();\r
+                for(Avatar avatar : avatarList){\r
+                    Player player = getPlayer(avatar);\r
+                    result.add(player);\r
+                }\r
+            }\r
+        }\r
+\r
+        return result;\r
+    }\r
+\r
+    /**\r
+     * プレイヤー一覧を得る。\r
+     * 参加エントリー順\r
+     * @return プレイヤーのリスト\r
+     */\r
+    public List<Player> getPlayerList(){\r
+        List<Player> result = Collections.unmodifiableList(this.playerList);\r
+        return result;\r
+    }\r
+\r
+    /**\r
+     * キャスティング表用にソートされたプレイヤー一覧を得る。\r
+     * @return プレイヤーのリスト\r
+     */\r
+    public List<Player> getCastingPlayerList(){\r
+        List<Player> sortedPlayers =\r
+                new LinkedList<Player>();\r
+        sortedPlayers.addAll(this.playerList);\r
+        Collections.sort(sortedPlayers, COMPARATOR_CASTING);\r
+        return sortedPlayers;\r
+    }\r
+\r
+    /**\r
+     * 指定された役職のプレイヤー一覧を得る。\r
+     * @param role 役職\r
+     * @return 役職に合致するプレイヤーのリスト\r
+     */\r
+    public List<Player> getRoledPlayerList(GameRole role){\r
+        List<Player> result = new LinkedList<Player>();\r
+\r
+        for(Player player : this.playerList){\r
+            if(player.getRole() == role){\r
+                result.add(player);\r
+            }\r
+        }\r
+\r
+        return result;\r
+    }\r
+\r
+    /**\r
+     * 勝利陣営を得る。\r
+     * @return 勝利した陣営\r
+     */\r
+    public Team getWinnerTeam(){\r
+        return this.winner;\r
+    }\r
+\r
+    /**\r
+     * 突然死者数を得る。\r
+     * @return 突然死者数\r
+     */\r
+    public int countSuddenDeath(){\r
+        int suddenDeath = 0;\r
+        for(Player player : this.playerList){\r
+            if(player.getDestiny() == Destiny.SUDDENDEATH) suddenDeath++;\r
+        }\r
+        return suddenDeath;\r
+    }\r
+\r
+    /**\r
+     * 参加プレイヤー総数を得る。\r
+     * @return プレイヤー総数\r
+     */\r
+    public int countAvatarNum(){\r
+        int playerNum = this.playerList.size();\r
+        return playerNum;\r
+    }\r
+\r
+    /**\r
+     * AvatarからPlayerを得る。\r
+     * 参加していないAvatarならnullを返す。\r
+     * @param avatar Avatar\r
+     * @return Player\r
+     */\r
+    public final Player getPlayer(Avatar avatar){\r
+        Player player = this.playerMap.get(avatar);\r
+        return player;\r
+    }\r
+\r
+    /**\r
+     * AvatarからPlayerを得る。\r
+     * 無ければ新規に作る。\r
+     * @param avatar Avatar\r
+     * @return Player\r
+     */\r
+    private Player registPlayer(Avatar avatar){\r
+        Player player = getPlayer(avatar);\r
+        if(player == null){\r
+            player = new Player();\r
+            player.setAvatar(avatar);\r
+            this.playerMap.put(avatar, player);\r
+        }\r
+        return player;\r
+    }\r
+\r
+    /**\r
+     * プレイヤーのソート仕様の記述。\r
+     * まとめサイトのキャスト表向け。\r
+     */\r
+    private static final class CastingComparator\r
+            implements Comparator<Player> {\r
+\r
+        /**\r
+         * コンストラクタ。\r
+         */\r
+        private CastingComparator(){\r
+            super();\r
+            return;\r
+        }\r
+\r
+        /**\r
+         * {@inheritDoc}\r
+         * @param p1 {@inheritDoc}\r
+         * @param p2 {@inheritDoc}\r
+         * @return {@inheritDoc}\r
+         */\r
+        public int compare(Player p1, Player p2){\r
+            if(p1 == p2) return 0;\r
+            if(p1 == null) return -1;\r
+            if(p2 == null) return +1;\r
+\r
+            // ゲルトが最前\r
+            Avatar avatar1 = p1.getAvatar();\r
+            Avatar avatar2 = p2.getAvatar();\r
+            if(avatar1.equals(avatar2)) return 0;\r
+            if(avatar1 == Avatar.AVATAR_GERD) return -1;\r
+            if(avatar2 == Avatar.AVATAR_GERD) return +1;\r
+\r
+            // 生存者は最後\r
+            Destiny dest1 = p1.getDestiny();\r
+            Destiny dest2 = p2.getDestiny();\r
+            if(dest1 != dest2){\r
+                if     (dest1 == Destiny.ALIVE) return +1;\r
+                else if(dest2 == Destiny.ALIVE) return -1;\r
+            }\r
+\r
+            // 退場順\r
+            int obitDay1 = p1.getObitDay();\r
+            int obitDay2 = p2.getObitDay();\r
+            if(obitDay1 > obitDay2) return +1;\r
+            if(obitDay1 < obitDay2) return -1;\r
+\r
+            // 運命順\r
+            int destinyOrder = dest1.compareTo(dest2);\r
+            if(destinyOrder != 0) return destinyOrder;\r
+\r
+            // エントリー順\r
+            int entryOrder = p1.getEntryNo() - p2.getEntryNo();\r
+\r
+            return entryOrder;\r
+        }\r
+    }\r
+\r
+    // TODO E国ハムスター対応\r
+}\r