OSDN Git Service

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