OSDN Git Service

空襲戦で女神で復活すると勝利判定がくるうのを直す
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / BattleInfo.cs
index 4643705..8f9d53f 100644 (file)
-// Copyright (C) 2014, 2015 Kazuhiro Fujieda <fujieda@users.sourceforge.jp>\r
+// Copyright (C) 2014, 2015 Kazuhiro Fujieda <fujieda@users.osdn.me>\r
 // \r
-// This program is part of KancolleSniffer.\r
+// Licensed under the Apache License, Version 2.0 (the "License");\r
+// you may not use this file except in compliance with the License.\r
+// You may obtain a copy of the License at\r
 //\r
-// KancolleSniffer is free software: you can redistribute it and/or modify\r
-// it under the terms of the GNU General Public License as published by\r
-// the Free Software Foundation, either version 3 of the License, or\r
-// (at your option) any later version.\r
+//    http://www.apache.org/licenses/LICENSE-2.0\r
 //\r
-// This program is distributed in the hope that it will be useful,\r
-// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
-// GNU General Public License for more details.\r
-//\r
-// You should have received a copy of the GNU General Public License\r
-// along with this program; if not, see <http://www.gnu.org/licenses/>.\r
+// Unless required by applicable law or agreed to in writing, software\r
+// distributed under the License is distributed on an "AS IS" BASIS,\r
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+// See the License for the specific language governing permissions and\r
+// limitations under the License.\r
 \r
 using System;\r
 using System.Collections.Generic;\r
 using System.Linq;\r
+using static System.Math;\r
 \r
 namespace KancolleSniffer\r
 {\r
+    public enum BattleResultRank\r
+    {\r
+        P,\r
+        S,\r
+        A,\r
+        B,\r
+        C,\r
+        D,\r
+        E\r
+    }\r
+\r
+    public enum BattleState\r
+    {\r
+        None,\r
+        Day,\r
+        Night,\r
+        Result,\r
+        Unknown\r
+    }\r
+\r
+    public class EnemyFighterPower\r
+    {\r
+        public bool HasUnknown { get; set; }\r
+        public string UnknownMark => HasUnknown ? "+" : "";\r
+        public int AirCombat { get; set; }\r
+        public int Interception { get; set; }\r
+    }\r
+\r
     public class BattleInfo\r
     {\r
-        private readonly ShipMaster _shipMaster;\r
         private readonly ShipInfo _shipInfo;\r
         private readonly ItemInfo _itemInfo;\r
         private int _fleet;\r
-        private int[] _friendHp;\r
-        private int[] _guardHp;\r
+        private Record[] _friend;\r
+        private Record[] _guard;\r
+        private Record[] _enemy;\r
+        private Record[] _enemyGuard;\r
         private readonly List<int> _escapingShips = new List<int>();\r
+        private bool _lastCell;\r
 \r
-        public bool InBattle { get; set; }\r
-        public string Formation { get; private set; }\r
-        public int EnemyAirSuperiority { get; private set; }\r
-        public bool HasDamagedShip { get; set; }\r
-        public string[] DamagedShipNames { get; private set; }\r
+        public BattleState BattleState { get; set; }\r
+        public int[] Formation { get; private set; }\r
+        public int[] FighterPower { get; private set; }\r
+        public EnemyFighterPower EnemyFighterPower { get; private set; }\r
         public int AirControlLevel { get; private set; }\r
+        public BattleResultRank ResultRank { get; private set; }\r
+        public RankPair DisplayedResultRank { get; } = new RankPair();\r
+        public BattleResult Result { get; set; }\r
+        public bool EnemyIsCombined => _enemyGuard.Length > 0;\r
+        public List<AirBattleResult> AirBattleResults { get; } = new List<AirBattleResult>();\r
 \r
-        public BattleInfo(ShipMaster shipMaster, ShipInfo shipInfo, ItemInfo itemInfo)\r
+        public class RankPair\r
         {\r
-            _shipMaster = shipMaster;\r
-            _shipInfo = shipInfo;\r
-            _itemInfo = itemInfo;\r
+            public char Assumed { get; set; }\r
+            public char Actual { get; set; }\r
+            public bool IsError => Assumed != Actual;\r
         }\r
 \r
-        public void InspectBattle(dynamic json)\r
+        public class BattleResult\r
         {\r
-            InBattle = true;\r
-            Formation = FormationName(json);\r
-            EnemyAirSuperiority = CalcEnemyAirSuperiority(json);\r
-            if (_friendHp != null)\r
-            {\r
-                ShowResult(false); // 昼戦の結果を夜戦のときに表示する\r
-            }\r
-            else\r
-            {\r
-                _fleet = (int)DeckId(json);\r
-                _friendHp = _shipInfo.GetShipStatuses(_fleet).Select(s => s.NowHp).ToArray();\r
-            }\r
-            if (IsNightBattle(json))\r
-            {\r
-                CalcHougekiDamage(json.api_hougeki, _friendHp);\r
-            }\r
-            else\r
+            public class Combined\r
             {\r
-                AirControlLevel = CheckAirControlLevel(json);\r
-                CalcDamage(json);\r
+                public ShipStatus[] Main { get; set; }\r
+                public ShipStatus[] Guard { get; set; }\r
             }\r
+\r
+            public Combined Friend { get; set; }\r
+            public Combined Enemy { get; set; }\r
+        }\r
+\r
+        public BattleInfo(ShipInfo shipInfo, ItemInfo itemInfo)\r
+        {\r
+            _shipInfo = shipInfo;\r
+            _itemInfo = itemInfo;\r
         }\r
 \r
-        private int DeckId(dynamic json)\r
+        public void InspectBattle(string url, string request, dynamic json)\r
+        {\r
+            if (json.api_formation())\r
+                Formation = ((dynamic[])json.api_formation).Select(f => f is string ? (int)int.Parse(f) : (int)f)\r
+                    .ToArray();\r
+            AirControlLevel = CheckAirControlLevel(json);\r
+            ShowResult(false); // 昼戦の結果を夜戦のときに表示する\r
+            SetupResult(request, json, url.Contains("practice"));\r
+            FighterPower = CalcFighterPower();\r
+            EnemyFighterPower = CalcEnemyFighterPower(json);\r
+            BattleState = IsNightBattle(json) ? BattleState.Night : BattleState.Day;\r
+            CalcDamage(json);\r
+            ResultRank = url.EndsWith("ld_airbattle") ? CalcLdAirBattleRank() : CalcResultRank();\r
+            SetResult();\r
+        }\r
+\r
+        private bool IsNightBattle(dynamic json) => json.api_hougeki();\r
+\r
+        public static int DeckId(dynamic json)\r
         {\r
             if (json.api_dock_id()) // 昼戦はtypoしている\r
                 return (int)json.api_dock_id - 1;\r
-            if (json.api_deck_id is string) // 通常の夜戦では文字列\r
+            if (json.api_deck_id is string) // é\80\9a常ã\81®å¤\9cæ\88¦ã\81¨é\80£å\90\88è\89¦é\9a\8a\91³æ\96¹ã\81®ã\81¿)ã\81§ã\81¯æ\96\87å­\97å\88\97\r
                 return int.Parse(json.api_deck_id) - 1;\r
             return (int)json.api_deck_id - 1;\r
         }\r
 \r
-        private bool IsNightBattle(dynamic json)\r
+        private void SetupResult(string request, dynamic json, bool practice)\r
         {\r
-            return json.api_hougeki();\r
+            if (_friend != null)\r
+                return;\r
+            _shipInfo.SaveBattleStartStatus();\r
+            _fleet = DeckId(json);\r
+            var fstats = _shipInfo.GetShipStatuses(_fleet);\r
+            FlagshipRecovery(request, fstats[0]);\r
+            _friend = Record.Setup(fstats, practice);\r
+            _guard = json.api_f_nowhps_combined()\r
+                ? Record.Setup(_shipInfo.GetShipStatuses(1), practice)\r
+                : new Record[0];\r
+            _enemy = Record.Setup((int[])json.api_e_nowhps,\r
+                ((int[])json.api_ship_ke).Select(_shipInfo.GetSpec).ToArray(),\r
+                ((int[][])json.api_eSlot).Select(slot => slot.Select(_itemInfo.GetSpecByItemId).ToArray()).ToArray(),\r
+                practice);\r
+            _enemyGuard = json.api_ship_ke_combined()\r
+                ? Record.Setup((int[])json.api_e_nowhps_combined,\r
+                    ((int[])json.api_ship_ke_combined).Select(_shipInfo.GetSpec).ToArray(),\r
+                    ((int[][])json.api_eSlot).Select(slot => slot.Select(_itemInfo.GetSpecByItemId).ToArray())\r
+                    .ToArray(), practice)\r
+                : new Record[0];\r
+        }\r
+\r
+        private void SetResult()\r
+        {\r
+            Result = new BattleResult\r
+            {\r
+                Friend = new BattleResult.Combined\r
+                {\r
+                    Main = _friend.Select(r => r.SnapShot).ToArray(),\r
+                    Guard = _guard.Select(r => r.SnapShot).ToArray()\r
+                },\r
+                Enemy = new BattleResult.Combined\r
+                {\r
+                    Main = _enemy.Select(r => r.SnapShot).ToArray(),\r
+                    Guard = _enemyGuard.Select(r => r.SnapShot).ToArray()\r
+                }\r
+            };\r
         }\r
 \r
-        private string FormationName(dynamic json)\r
+        private void FlagshipRecovery(string request, ShipStatus flagship)\r
         {\r
-            if (!json.api_formation()) // 演習の夜戦\r
-                return "";\r
-            switch ((int)json.api_formation[2])\r
+            var type = int.Parse(HttpUtility.ParseQueryString(request)["api_recovery_type"] ?? "0");\r
+            switch (type)\r
             {\r
+                case 0:\r
+                    return;\r
                 case 1:\r
-                    return "同航戦";\r
+                    flagship.NowHp = flagship.MaxHp / 2;\r
+                    ConsumeSlotItem(flagship, 42); // ダメコン\r
+                    break;\r
                 case 2:\r
-                    return "反航戦";\r
-                case 3:\r
-                    return "T字有利";\r
-                case 4:\r
-                    return "T字不利";\r
+                    flagship.NowHp = flagship.MaxHp;\r
+                    ConsumeSlotItem(flagship, 43); // 女神\r
+                    break;\r
+            }\r
+            if (type != 0)\r
+                _shipInfo.SetBadlyDamagedShips();\r
+        }\r
+\r
+        private static void ConsumeSlotItem(ShipStatus ship, int id)\r
+        {\r
+            if (ship.SlotEx.Spec.Id == id)\r
+            {\r
+                ship.SlotEx = new ItemStatus();\r
+                return;\r
             }\r
-            return "";\r
+            for (var i = 0; i < ship.Slot.Length; i++)\r
+            {\r
+                if (ship.Slot[i].Spec.Id == id)\r
+                {\r
+                    ship.Slot[i] = new ItemStatus();\r
+                    break;\r
+                }\r
+            }\r
+        }\r
+\r
+        public void CleanupResult()\r
+        {\r
+            _friend = null;\r
+            _lastCell = false;\r
         }\r
 \r
         private int CheckAirControlLevel(dynamic json)\r
         {\r
+            if (!json.api_kouku())\r
+                return -1;\r
             var stage1 = json.api_kouku.api_stage1;\r
+            if (stage1 == null)\r
+                return -1;\r
             if (stage1.api_f_count == 0 && stage1.api_e_count == 0)\r
                 return -1;\r
             return (int)stage1.api_disp_seiku;\r
         }\r
 \r
-        private int CalcEnemyAirSuperiority(dynamic json)\r
+        private int[] CalcFighterPower()\r
+        {\r
+            if (_guard.Length > 0 && _enemyGuard.Length > 0)\r
+                return _shipInfo.GetFighterPower(0).Zip(_shipInfo.GetFighterPower(1), (a, b) => a + b).ToArray();\r
+            return _shipInfo.GetFighterPower(_fleet);\r
+        }\r
+\r
+        private EnemyFighterPower CalcEnemyFighterPower(dynamic json)\r
         {\r
-            var maxEq = ((int[])json.api_ship_ke).Skip(1).SelectMany(id => _shipMaster[id].MaxEq);\r
+            var result = new EnemyFighterPower();\r
+            var ships = (int[])json.api_ship_ke;\r
+            if (json.api_ship_ke_combined() && _guard.Length > 0)\r
+                ships = ships.Concat((int[])json.api_ship_ke_combined).ToArray();\r
+            var maxEq = ships.SelectMany(id =>\r
+            {\r
+                var r = _shipInfo.GetSpec(id).MaxEq;\r
+                if (r != null)\r
+                    return r;\r
+                result.HasUnknown = true;\r
+                return new int[5];\r
+            });\r
             var equips = ((int[][])json.api_eSlot).SelectMany(x => x);\r
-            return (from slot in equips.Zip(maxEq, (id, max) => new {id, max})\r
+            if (json.api_eSlot_combined() && _guard.Length > 0)\r
+                equips = equips.Concat(((int[][])json.api_eSlot_combined).SelectMany(x => x));\r
+            foreach (var entry in from slot in equips.Zip(maxEq, (id, max) => new {id, max})\r
                 let spec = _itemInfo.GetSpecByItemId(slot.id)\r
-                where spec.CanAirCombat()\r
-                select (int)Math.Floor(spec.AntiAir * Math.Sqrt(slot.max))).DefaultIfEmpty().Sum();\r
+                let perSlot = (int)Floor(spec.AntiAir * Sqrt(slot.max))\r
+                select new {spec, perSlot})\r
+            {\r
+                if (entry.spec.CanAirCombat)\r
+                    result.AirCombat += entry.perSlot;\r
+                if (entry.spec.IsAircraft)\r
+                    result.Interception += entry.perSlot;\r
+            }\r
+            return result;\r
+        }\r
+\r
+        private enum CombatType\r
+        {\r
+            AtOnce,\r
+            ByTurn,\r
+            Support,\r
+            Aircraft,\r
+            AirBase,\r
+            Friend\r
+        }\r
+\r
+        private class Phase\r
+        {\r
+            public string Api { get; }\r
+            public CombatType Type { get; }\r
+            public string Name { get; }\r
+\r
+            public Phase(string api, CombatType type, string name = "")\r
+            {\r
+                Api = api;\r
+                Type = type;\r
+                Name = name;\r
+            }\r
         }\r
 \r
         private void CalcDamage(dynamic json)\r
         {\r
-            if (json.api_kouku.api_stage3 != null)\r
-                CalcSimpleDamage(json.api_kouku.api_stage3.api_fdam, _friendHp);\r
-            if (json.api_kouku2() && json.api_kouku2.api_stage3 != null) // 航空戦2回目\r
-                CalcSimpleDamage(json.api_kouku2.api_stage3.api_fdam, _friendHp);\r
-            if (!json.api_opening_atack()) // 航空戦のみ\r
+            AirBattleResults.Clear();\r
+            var phases = new[]\r
+            {\r
+                new Phase("air_base_injection", CombatType.Aircraft, "AB噴式"),\r
+                new Phase("injection_kouku", CombatType.Aircraft, "噴式"),\r
+                new Phase("air_base_attack", CombatType.AirBase),\r
+                new Phase("n_support_info", CombatType.Support),\r
+                new Phase("n_hougeki1", CombatType.ByTurn),\r
+                new Phase("n_hougeki2", CombatType.ByTurn),\r
+                new Phase("kouku", CombatType.Aircraft, "航空戦"),\r
+                new Phase("kouku2", CombatType.Aircraft, "航空戦2"),\r
+                new Phase("support_info", CombatType.Support),\r
+                new Phase("opening_taisen", CombatType.ByTurn),\r
+                new Phase("opening_atack", CombatType.AtOnce),\r
+                new Phase("friendly_battle", CombatType.Friend),\r
+                new Phase("hougeki", CombatType.ByTurn),\r
+                new Phase("hougeki1", CombatType.ByTurn),\r
+                new Phase("hougeki2", CombatType.ByTurn),\r
+                new Phase("hougeki3", CombatType.ByTurn),\r
+                new Phase("raigeki", CombatType.AtOnce)\r
+            };\r
+            foreach (var phase in phases)\r
+                CalcDamageByType(json, phase);\r
+        }\r
+\r
+        private void CalcDamageByType(dynamic json, Phase phase)\r
+        {\r
+            var api = "api_" + phase.Api;\r
+            if (!json.IsDefined(api) || json[api] == null)\r
                 return;\r
-            if (json.api_opening_atack != null)\r
-                CalcSimpleDamage(json.api_opening_atack.api_fdam, _friendHp);\r
-            if (json.api_hougeki1 != null)\r
-                CalcHougekiDamage(json.api_hougeki1, _friendHp);\r
-            if (json.api_hougeki2 != null)\r
-                CalcHougekiDamage(json.api_hougeki2, _friendHp);\r
-            if (json.api_raigeki != null)\r
-                CalcSimpleDamage(json.api_raigeki.api_fdam, _friendHp);\r
+            switch (phase.Type)\r
+            {\r
+                case CombatType.AtOnce:\r
+                    CalcDamageAtOnce(json[api]);\r
+                    break;\r
+                case CombatType.ByTurn:\r
+                    CalcDamageByTurn(json[api]);\r
+                    break;\r
+                case CombatType.Support:\r
+                    CalcSupportDamage(json[api]);\r
+                    break;\r
+                case CombatType.Aircraft:\r
+                    AddAirBattleResult(json[api], phase.Name);\r
+                    CalcKoukuDamage(json[api]);\r
+                    break;\r
+                case CombatType.AirBase:\r
+                    CalcAirBaseAttackDamage(json[api]);\r
+                    break;\r
+                case CombatType.Friend:\r
+                    CalcFriendAttackDamage(json[api]);\r
+                    break;\r
+            }\r
         }\r
 \r
-        private void CalcSimpleDamage(dynamic rawDamage, int[] result)\r
+        private void CalcSupportDamage(dynamic json)\r
+        {\r
+            if (json.api_support_hourai != null)\r
+            {\r
+                CalcRawDamageAtOnce(json.api_support_hourai.api_damage, _enemy, _enemyGuard);\r
+            }\r
+            else if (json.api_support_airatack != null)\r
+            {\r
+                CalcRawDamageAtOnce(json.api_support_airatack.api_stage3.api_edam, _enemy, _enemyGuard);\r
+            }\r
+        }\r
+\r
+        private void CalcAirBaseAttackDamage(dynamic json)\r
+        {\r
+            var i = 1;\r
+            foreach (var entry in json)\r
+            {\r
+                AddAirBattleResult(entry, "基地" + i++);\r
+                CalcKoukuDamage(entry);\r
+            }\r
+        }\r
+\r
+        private void CalcFriendAttackDamage(dynamic json)\r
+        {\r
+            CalcDamageByTurn(json.api_hougeki, true);\r
+        }\r
+\r
+        private void AddAirBattleResult(dynamic json, string phaseName)\r
+        {\r
+            var stage1 = json.api_stage1;\r
+            if (stage1 == null || (stage1.api_f_count == 0 && stage1.api_e_count == 0))\r
+                return;\r
+            var result = new AirBattleResult\r
+            {\r
+                PhaseName = phaseName,\r
+                AirControlLevel = json.api_stage1.api_disp_seiku() ? (int)json.api_stage1.api_disp_seiku : 0,\r
+                Stage1 = new AirBattleResult.StageResult\r
+                {\r
+                    FriendCount = (int)json.api_stage1.api_f_count,\r
+                    FriendLost = (int)json.api_stage1.api_f_lostcount,\r
+                    EnemyCount = (int)json.api_stage1.api_e_count,\r
+                    EnemyLost = (int)json.api_stage1.api_e_lostcount\r
+                },\r
+                Stage2 = json.api_stage2 == null\r
+                    ? new AirBattleResult.StageResult\r
+                    {\r
+                        FriendCount = 0,\r
+                        FriendLost = 0,\r
+                        EnemyCount = 0,\r
+                        EnemyLost = 0\r
+                    }\r
+                    : new AirBattleResult.StageResult\r
+                    {\r
+                        FriendCount = (int)json.api_stage2.api_f_count,\r
+                        FriendLost = (int)json.api_stage2.api_f_lostcount,\r
+                        EnemyCount = (int)json.api_stage2.api_e_count,\r
+                        EnemyLost = (int)json.api_stage2.api_e_lostcount\r
+                    }\r
+            };\r
+            if (json.api_stage2 != null && json.api_stage2.api_air_fire())\r
+            {\r
+                var airfire = json.api_stage2.api_air_fire;\r
+                var idx = (int)airfire.api_idx;\r
+                result.AirFire = new AirBattleResult.AirFireResult\r
+                {\r
+                    ShipName = idx < _friend.Length ? _friend[idx].Name : _guard[idx - 6].Name,\r
+                    Kind = (int)airfire.api_kind,\r
+                    Items = ((int[])airfire.api_use_items).Select(id => _itemInfo.GetSpecByItemId(id).Name).ToArray()\r
+                };\r
+            }\r
+            AirBattleResults.Add(result);\r
+        }\r
+\r
+        private void CalcKoukuDamage(dynamic json)\r
+        {\r
+            if (json.api_stage3() && json.api_stage3 != null)\r
+                CalcDamageAtOnce(json.api_stage3, _friend, _enemy);\r
+            if (json.api_stage3_combined() && json.api_stage3_combined != null)\r
+                CalcDamageAtOnce(json.api_stage3_combined, _guard, _enemyGuard);\r
+        }\r
+\r
+        private void CalcDamageAtOnce(dynamic json)\r
+        {\r
+            CalcDamageAtOnce(json, _friend, _guard, _enemy, _enemyGuard);\r
+        }\r
+\r
+        private void CalcDamageAtOnce(dynamic json, Record[] friend, Record[] enemy)\r
+        {\r
+            CalcDamageAtOnce(json, friend, null, enemy, null);\r
+        }\r
+\r
+        private void CalcDamageAtOnce(dynamic json,\r
+            Record[] friend, Record[] guard, Record[] enemy, Record[] enemyGuard)\r
+        {\r
+            if (json.api_fdam() && json.api_fdam != null)\r
+                CalcRawDamageAtOnce(json.api_fdam, friend, guard);\r
+            if (json.api_edam() && json.api_edam != null)\r
+                CalcRawDamageAtOnce(json.api_edam, enemy, enemyGuard);\r
+        }\r
+\r
+        private void CalcRawDamageAtOnce(dynamic rawDamage, Record[] friend, Record[] guard = null)\r
         {\r
             var damage = (int[])rawDamage;\r
-            for (var i = 0; i < _friendHp.Length; i++)\r
-                result[i] -= damage[i + 1];\r
+            for (var i = 0; i < friend.Length; i++)\r
+                friend[i].ApplyDamage(damage[i]);\r
+            if (guard == null)\r
+                return;\r
+            for (var i = 0; i < guard.Length; i++)\r
+                guard[i].ApplyDamage(damage[i + 6]);\r
         }\r
 \r
-        private void CalcHougekiDamage(dynamic hougeki, int[] friend)\r
+        private void CalcDamageByTurn(dynamic json, bool ignoreFriendDamage = false)\r
         {\r
-            var targets = ((dynamic[])hougeki.api_df_list).Skip(1).SelectMany(x => (int[])x);\r
-            var damages = ((dynamic[])hougeki.api_damage).Skip(1).SelectMany(x => (double[])x);\r
-            foreach (var hit in targets.Zip(damages, (t, d) => new {t, d}))\r
+            if (!(json.api_df_list() && json.api_df_list != null &&\r
+                  json.api_damage() && json.api_damage != null &&\r
+                  json.api_at_eflag() && json.api_at_eflag != null))\r
+                return;\r
+\r
+            var targets = (int[][])json.api_df_list;\r
+            var damages = (int[][])json.api_damage;\r
+            var eflags = (int[])json.api_at_eflag;\r
+            var records = new[] {new Record[12], new Record[12]};\r
+            Array.Copy(_friend, records[1], _friend.Length);\r
+            Array.Copy(_guard, 0, records[1], 6, _guard.Length);\r
+            Array.Copy(_enemy, records[0], _enemy.Length);\r
+            Array.Copy(_enemyGuard, 0, records[0], 6, _enemyGuard.Length);\r
+            for (var i = 0; i < eflags.Length; i++)\r
             {\r
-                if (1 <= hit.t && hit.t <= friend.Length)\r
-                    friend[hit.t - 1] -= (int)hit.d;\r
+                // 一度に複数の目標を狙う攻撃はないものと仮定する\r
+                var hit = new {t = targets[i][0], d = damages[i].Sum(d => d >= 0 ? d : 0)};\r
+                if (hit.t == -1)\r
+                    continue;\r
+                if (ignoreFriendDamage && eflags[i] == 1)\r
+                    continue;\r
+                records[eflags[i]][hit.t].ApplyDamage(hit.d);\r
             }\r
         }\r
 \r
+        public void InspectMapStart(dynamic json)\r
+        {\r
+            InspectMapNext(json);\r
+        }\r
+\r
+        public void InspectMapNext(dynamic json)\r
+        {\r
+            _lastCell = (int)json.api_next == 0;\r
+        }\r
+\r
         public void InspectBattleResult(dynamic json)\r
         {\r
-            ShowResult();\r
-            _friendHp = null;\r
+            BattleState = BattleState.Result;\r
+            ShowResult(!_lastCell);\r
+            _shipInfo.SaveBattleResult();\r
+            VerifyResultRank(json);\r
+            CleanupResult();\r
+            SetEscapeShips(json);\r
+        }\r
+\r
+        private void VerifyResultRank(dynamic json)\r
+        {\r
+            if (_friend == null)\r
+                return;\r
+            if (!json.api_win_rank())\r
+                return;\r
+            var assumed = "PSABCDE"[(int)ResultRank];\r
+            if (assumed == 'P')\r
+                assumed = 'S';\r
+            var actual = ((string)json.api_win_rank)[0];\r
+            DisplayedResultRank.Assumed = assumed;\r
+            DisplayedResultRank.Actual = actual;\r
         }\r
 \r
         public void InspectPracticeResult(dynamic json)\r
         {\r
+            BattleState = BattleState.Result;\r
             ShowResult(false);\r
-            _friendHp = null;\r
+            VerifyResultRank(json);\r
+            CleanupResult();\r
         }\r
 \r
         private void ShowResult(bool warnDamagedShip = true)\r
         {\r
-            var ships = _shipInfo.GetShipStatuses(_fleet);\r
-            foreach (var e in ships.Zip(_friendHp, (ship, now) => new {ship, now}))\r
-                e.ship.NowHp = e.now;\r
+            if (_friend == null)\r
+                return;\r
+            var ships = _guard.Length > 0\r
+                ? _shipInfo.GetShipStatuses(0).Concat(_shipInfo.GetShipStatuses(1)).ToArray()\r
+                : _shipInfo.GetShipStatuses(_fleet);\r
+            foreach (var entry in ships.Zip(_friend.Concat(_guard), (ship, now) => new {ship, now}))\r
+                entry.now.UpdateShipStatus(entry.ship);\r
             if (warnDamagedShip)\r
-                UpdateDamgedShipNames(ships);\r
-        }\r
-\r
-        private void UpdateDamgedShipNames(IEnumerable<ShipStatus> ships)\r
-        {\r
-            DamagedShipNames =\r
-                (from ship in ships where ship.DamageLevel == ShipStatus.Damage.Badly select ship.Name).ToArray();\r
-            HasDamagedShip = DamagedShipNames.Any();\r
+                _shipInfo.SetBadlyDamagedShips();\r
+            else\r
+                _shipInfo.ClearBadlyDamagedShips();\r
         }\r
 \r
-        public void InspectCombinedBattle(dynamic json, bool surfaceFleet)\r
+        public void SetEscapeShips(dynamic json)\r
         {\r
-            InBattle = true;\r
-            Formation = FormationName(json);\r
-            EnemyAirSuperiority = CalcEnemyAirSuperiority(json);\r
-            if (_friendHp != null)\r
+            _escapingShips.Clear();\r
+            if (!json.api_escape_flag() || (int)json.api_escape_flag == 0)\r
+                return;\r
+            var damaged = (int)json.api_escape.api_escape_idx[0] - 1;\r
+            if (json.api_escape.api_tow_idx())\r
             {\r
-                ShowResultCombined(false);\r
+                _escapingShips.Add(_shipInfo.GetDeck(damaged / 6)[damaged % 6]);\r
+                var escort = (int)json.api_escape.api_tow_idx[0] - 1;\r
+                _escapingShips.Add(_shipInfo.GetDeck(escort / 6)[escort % 6]);\r
             }\r
             else\r
             {\r
-                _fleet = 10;\r
-                _friendHp = _shipInfo.GetShipStatuses(0).Select(s => s.NowHp).ToArray();\r
-                _guardHp = _shipInfo.GetShipStatuses(1).Select(s => s.NowHp).ToArray();\r
+                _escapingShips.Add(_shipInfo.GetDeck(2)[damaged]);\r
             }\r
-            if (IsNightBattle(json))\r
+        }\r
+\r
+        public void CauseEscape()\r
+        {\r
+            _shipInfo.SetEscapedShips(_escapingShips);\r
+            _shipInfo.SetBadlyDamagedShips();\r
+        }\r
+\r
+        private class Record\r
+        {\r
+            private ShipStatus _status;\r
+            private bool _practice;\r
+            public ShipStatus SnapShot => (ShipStatus)_status.Clone();\r
+            public int NowHp => _status.NowHp;\r
+            public bool Escaped => _status.Escaped;\r
+            public ShipStatus.Damage DamageLevel => _status.DamageLevel;\r
+            public string Name => _status.Name;\r
+            public int StartHp { get; private set; }\r
+\r
+            public static Record[] Setup(ShipStatus[] ships, bool practice) =>\r
+            (from s in ships\r
+                select new Record {_status = (ShipStatus)s.Clone(), _practice = practice, StartHp = s.NowHp}).ToArray();\r
+\r
+            public static Record[] Setup(int[] nowhps, ShipSpec[] ships, ItemSpec[][] slots, bool practice)\r
             {\r
-                CalcHougekiDamage(json.api_hougeki, _guardHp);\r
+                return Enumerable.Range(0, nowhps.Length).Select(i =>\r
+                    new Record\r
+                    {\r
+                        StartHp = nowhps[i],\r
+                        _status = new ShipStatus\r
+                        {\r
+                            Id = ships[i].Id,\r
+                            NowHp = nowhps[i],\r
+                            MaxHp = nowhps[i],\r
+                            Spec = ships[i],\r
+                            Slot = slots[i].Select(spec => new ItemStatus {Id = spec.Id, Spec = spec}).ToArray(),\r
+                            SlotEx = new ItemStatus(0)\r
+                        },\r
+                        _practice = practice\r
+                    }).ToArray();\r
             }\r
-            else\r
+\r
+            public void ApplyDamage(int damage)\r
             {\r
-                AirControlLevel = CheckAirControlLevel(json);\r
-                if (surfaceFleet)\r
-                    CalcDamageCombinedFleetSurface(json);\r
-                else\r
-                    CalcDamageCombinedFleetAir(json);\r
+                if (_status.NowHp > damage)\r
+                {\r
+                    _status.NowHp -= damage;\r
+                    return;\r
+                }\r
+                _status.NowHp = 0;\r
+                if (_practice)\r
+                    return;\r
+                foreach (var item in new[] {_status.SlotEx}.Concat(_status.Slot))\r
+                {\r
+                    if (item.Spec.Id == 42)\r
+                    {\r
+                        _status.NowHp = (int)(_status.MaxHp * 0.2);\r
+                        ConsumeSlotItem(_status, 42);\r
+                        break;\r
+                    }\r
+                    if (item.Spec.Id == 43)\r
+                    {\r
+                        _status.NowHp = _status.MaxHp;\r
+                        ConsumeSlotItem(_status, 43);\r
+                        break;\r
+                    }\r
+                }\r
             }\r
-        }\r
 \r
-        public void InspectCombinedBattleResult(dynamic json)\r
-        {\r
-            _escapingShips.Clear();\r
-            ShowResultCombined();\r
-            _friendHp = null;\r
-            if ((int)json.api_escape_flag == 0)\r
-                return;\r
-            var damaged = (int)json.api_escape.api_escape_idx[0] - 1;\r
-            _escapingShips.Add(_shipInfo.GetDeck(damaged / 6)[damaged % 6]);\r
-            var escort = (int)json.api_escape.api_tow_idx[0] - 1;\r
-            _escapingShips.Add(_shipInfo.GetDeck(escort / 6)[escort % 6]);\r
+            public void UpdateShipStatus(ShipStatus ship)\r
+            {\r
+                ship.NowHp = NowHp;\r
+                ship.Slot = _status.Slot;\r
+                ship.SlotEx = _status.SlotEx;\r
+            }\r
         }\r
 \r
-        private void ShowResultCombined(bool warnDamagedShip = true)\r
+        private BattleResultRank CalcLdAirBattleRank()\r
         {\r
-            var ships = _shipInfo.GetShipStatuses(0).Concat(_shipInfo.GetShipStatuses(1)).ToArray();\r
-            foreach (var e in ships.Zip(_friendHp.Concat(_guardHp), (ship, now) => new {ship, now}))\r
-                e.ship.NowHp = e.now;\r
-            if (warnDamagedShip)\r
-                UpdateDamgedShipNames(ships);\r
+            var combined = _friend.Concat(_guard).Where(r => !r.Escaped).ToArray();\r
+            var friendGauge = combined.Sum(r => r.StartHp - r.NowHp);\r
+            var friendGaugeRate = Floor((double)friendGauge / combined.Sum(r => r.StartHp) * 100);\r
+\r
+            if (friendGauge <= 0)\r
+                return BattleResultRank.P;\r
+            if (friendGaugeRate < 10)\r
+                return BattleResultRank.A;\r
+            if (friendGaugeRate < 20)\r
+                return BattleResultRank.B;\r
+            if (friendGaugeRate < 50)\r
+                return BattleResultRank.C;\r
+            if (friendGaugeRate < 80)\r
+                return BattleResultRank.D;\r
+            return BattleResultRank.E;\r
         }\r
 \r
-        public void CauseCombinedBattleEscape()\r
+        private BattleResultRank CalcResultRank()\r
         {\r
-            _shipInfo.SetEscapedShips(_escapingShips);\r
-            UpdateDamgedShipNames(_shipInfo.GetShipStatuses(0).Concat(_shipInfo.GetShipStatuses(1)));\r
+            var friend = _friend.Concat(_guard).ToArray();\r
+            var enemy = _enemy.Concat(_enemyGuard).ToArray();\r
+\r
+            var friendCount = friend.Length;\r
+            var friendStartHpTotal = 0;\r
+            var friendNowHpTotal = 0;\r
+            var friendSunk = 0;\r
+            foreach (var ship in friend)\r
+            {\r
+                if (ship.Escaped)\r
+                    continue;\r
+                friendStartHpTotal += ship.StartHp;\r
+                friendNowHpTotal += ship.NowHp;\r
+                if (ship.NowHp == 0)\r
+                    friendSunk++;\r
+            }\r
+            var friendGaugeRate = (int)((double)(friendStartHpTotal - friendNowHpTotal) / friendStartHpTotal * 100);\r
+\r
+            var enemyCount = enemy.Length;\r
+            var enemyStartHpTotal = enemy.Sum(r => r.StartHp);\r
+            var enemyNowHpTotal = enemy.Sum(r => r.NowHp);\r
+            var enemySunk = enemy.Count(r => r.NowHp == 0);\r
+            var enemyGaugeRate = (int)((double)(enemyStartHpTotal - enemyNowHpTotal) / enemyStartHpTotal * 100);\r
+\r
+            if (friendSunk == 0 && enemySunk == enemyCount)\r
+            {\r
+                if (friendNowHpTotal >= friendStartHpTotal)\r
+                    return BattleResultRank.P;\r
+                return BattleResultRank.S;\r
+            }\r
+            if (friendSunk == 0 && enemySunk >= (int)(enemyCount * 0.7) && enemyCount > 1)\r
+                return BattleResultRank.A;\r
+            if (friendSunk < enemySunk && enemy[0].NowHp == 0)\r
+                return BattleResultRank.B;\r
+            if (friendCount == 1 && friend[0].DamageLevel == ShipStatus.Damage.Badly)\r
+                return BattleResultRank.D;\r
+            if (enemyGaugeRate > friendGaugeRate * 2.5)\r
+                return BattleResultRank.B;\r
+            if (enemyGaugeRate > friendGaugeRate * 0.9)\r
+                return BattleResultRank.C;\r
+            if (friendCount > 1 && friendCount - 1 == friendSunk)\r
+                return BattleResultRank.E;\r
+            return BattleResultRank.D;\r
         }\r
 \r
-        private void CalcDamageCombinedFleetAir(dynamic json)\r
+        /// <summary>\r
+        /// テスト専用\r
+        /// </summary>\r
+        public void InjectResultStatus(ShipStatus[] main, ShipStatus[] guard, ShipStatus[] enemy, ShipStatus[] enemyGuard)\r
         {\r
-            var kouku = json.api_kouku;\r
-            if (kouku.api_stage3 != null)\r
-                CalcSimpleDamage(kouku.api_stage3.api_fdam, _friendHp);\r
-            if (kouku.api_stage3_combined != null)\r
-                CalcSimpleDamage(kouku.api_stage3_combined.api_fdam, _guardHp);\r
-            if (json.api_kouku2()) // 航空戦2回目\r
+            Result = new BattleResult\r
             {\r
-                kouku = json.api_kouku2;\r
-                if (kouku.api_stage3 != null)\r
-                    CalcSimpleDamage(kouku.api_stage3.api_fdam, _friendHp);\r
-                if (kouku.api_stage3_combined != null)\r
-                    CalcSimpleDamage(kouku.api_stage3_combined.api_fdam, _guardHp);\r
-            }\r
-            if (!json.api_opening_atack()) // 航空戦のみ\r
-                return;\r
-            if (json.api_opening_atack != null)\r
-                CalcSimpleDamage(json.api_opening_atack.api_fdam, _guardHp);\r
-            if (json.api_hougeki1 != null)\r
-                CalcHougekiDamage(json.api_hougeki1, _guardHp);\r
-            if (json.api_hougeki2() && json.api_hougeki2 != null)\r
-                CalcHougekiDamage(json.api_hougeki2, _friendHp);\r
-            if (json.api_hougeki3() && json.api_hougeki3 != null)\r
-                CalcHougekiDamage(json.api_hougeki3, _friendHp);\r
-            if (json.api_raigeki() && json.api_raigeki != null)\r
-                CalcSimpleDamage(json.api_raigeki.api_fdam, _guardHp);\r
-        }\r
-\r
-        private void CalcDamageCombinedFleetSurface(dynamic json)\r
-        {\r
-            var kouku = json.api_kouku;\r
-            if (kouku.api_stage3 != null)\r
-                CalcSimpleDamage(kouku.api_stage3.api_fdam, _friendHp);\r
-            if (kouku.api_stage3_combined != null)\r
-                CalcSimpleDamage(kouku.api_stage3_combined.api_fdam, _guardHp);\r
-            if (json.api_opening_atack != null)\r
-                CalcSimpleDamage(json.api_opening_atack.api_fdam, _guardHp);\r
-            if (json.api_hougeki1 != null)\r
-                CalcHougekiDamage(json.api_hougeki1, _friendHp);\r
-            if (json.api_hougeki2() && json.api_hougeki2 != null)\r
-                CalcHougekiDamage(json.api_hougeki2, _friendHp);\r
-            if (json.api_hougeki3() && json.api_hougeki3 != null)\r
-                CalcHougekiDamage(json.api_hougeki3, _guardHp);\r
-            if (json.api_raigeki() && json.api_raigeki != null)\r
-                CalcSimpleDamage(json.api_raigeki.api_fdam, _guardHp);\r
+                Friend = new BattleResult.Combined { Main = main, Guard = guard},\r
+                Enemy = new BattleResult.Combined {Main = enemy, Guard = enemyGuard}\r
+            };\r
         }\r
     }\r
 }
\ No newline at end of file