--- /dev/null
+/*\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