OSDN Git Service

連合艦隊同士の雷撃戦のダメージ計算を間違えているのを直す
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / BattleInfo.cs
1 // Copyright (C) 2014, 2015 Kazuhiro Fujieda <fujieda@users.osdn.me>\r
2 // \r
3 // Licensed under the Apache License, Version 2.0 (the "License");\r
4 // you may not use this file except in compliance with the License.\r
5 // You may obtain a copy of the License at\r
6 //\r
7 //    http://www.apache.org/licenses/LICENSE-2.0\r
8 //\r
9 // Unless required by applicable law or agreed to in writing, software\r
10 // distributed under the License is distributed on an "AS IS" BASIS,\r
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
12 // See the License for the specific language governing permissions and\r
13 // limitations under the License.\r
14 \r
15 using System.Collections.Generic;\r
16 using System.Linq;\r
17 using static System.Math;\r
18 \r
19 namespace KancolleSniffer\r
20 {\r
21     public enum BattleResultRank\r
22     {\r
23         P,\r
24         S,\r
25         A,\r
26         B,\r
27         C,\r
28         D,\r
29         E,\r
30     }\r
31 \r
32     public class BattleInfo\r
33     {\r
34         private readonly ShipInfo _shipInfo;\r
35         private readonly ItemInfo _itemInfo;\r
36         private int _fleet;\r
37         private Record[] _friend;\r
38         private Record[] _guard;\r
39         private int[] _enemyHp;\r
40         private int[] _enemyGuardHp;\r
41         private int[] _enemyStartHp;\r
42         private int[] _enemyGuardStartHp;\r
43         private readonly List<int> _escapingShips = new List<int>();\r
44         private int _flagshipRecoveryType;\r
45 \r
46         public bool InBattle { get; set; }\r
47         public string Formation { get; private set; }\r
48         public string EnemyFighterPower { get; private set; }\r
49         public int AirControlLevel { get; private set; }\r
50         public BattleResultRank ResultRank { get; private set; }\r
51         public ShipStatus[] EnemyResultStatus { get; private set; }\r
52 \r
53         public BattleInfo(ShipInfo shipInfo, ItemInfo itemInfo)\r
54         {\r
55             _shipInfo = shipInfo;\r
56             _itemInfo = itemInfo;\r
57         }\r
58 \r
59         public void InspectBattle(dynamic json, string url)\r
60         {\r
61             InBattle = true;\r
62             Formation = FormationName(json);\r
63             EnemyFighterPower = CalcEnemyFighterPower(json);\r
64             AirControlLevel = CheckAirControlLevel(json);\r
65             ShowResult(false); // 昼戦の結果を夜戦のときに表示する\r
66             SetupResult(json);\r
67             if (IsNightBattle(json))\r
68             {\r
69                 CalcHougekiDamage(json.api_hougeki,\r
70                     _guard.Length > 0 ? _guard : _friend,\r
71                     json.api_active_deck() && json.api_active_deck[1] != 1 ? _enemyGuardHp : _enemyHp);\r
72             }\r
73             else\r
74             {\r
75                 CalcDamage(json, url.EndsWith("battle_water"));\r
76             }\r
77             ClearEnemyOverKill();\r
78             ResultRank = url.EndsWith("ld_airbattle") ? CalcLdAirBattleRank() : CalcResultRank();\r
79         }\r
80 \r
81         private void ClearEnemyOverKill()\r
82         {\r
83             _enemyHp = _enemyHp.Select(hp => hp < 0 ? 0 : hp).ToArray();\r
84             _enemyGuardHp = _enemyGuardHp.Select(hp => hp < 0 ? 0 : hp).ToArray();\r
85         }\r
86 \r
87         public void InspectMapNext(string request)\r
88         {\r
89             var type = HttpUtility.ParseQueryString(request)["api_recovery_type"];\r
90             if (type == null)\r
91                 return;\r
92             _flagshipRecoveryType = int.Parse(type);\r
93         }\r
94 \r
95         private bool IsNightBattle(dynamic json) => json.api_hougeki();\r
96 \r
97         private int DeckId(dynamic json)\r
98         {\r
99             if (json.api_dock_id()) // 昼戦はtypoしている\r
100                 return (int)json.api_dock_id - 1;\r
101             if (json.api_deck_id is string) // 通常の夜戦では文字列\r
102                 return int.Parse(json.api_deck_id) - 1;\r
103             return (int)json.api_deck_id - 1;\r
104         }\r
105 \r
106         private string FormationName(dynamic json)\r
107         {\r
108             if (!json.api_formation()) // 演習の夜戦\r
109                 return "";\r
110             switch ((int)json.api_formation[2])\r
111             {\r
112                 case 1:\r
113                     return "同航戦";\r
114                 case 2:\r
115                     return "反航戦";\r
116                 case 3:\r
117                     return "T字有利";\r
118                 case 4:\r
119                     return "T字不利";\r
120             }\r
121             return "";\r
122         }\r
123 \r
124         private void SetupResult(dynamic json)\r
125         {\r
126             if (_friend != null)\r
127                 return;\r
128             var nowhps = (int[])json.api_nowhps;\r
129             _fleet = DeckId(json);\r
130             var fstats = _shipInfo.GetShipStatuses(_fleet);\r
131             FlagshipRecovery(fstats[0]);\r
132             _friend = Record.Setup(fstats);\r
133             _enemyHp = nowhps.Skip(7).TakeWhile(hp => hp != -1).ToArray();\r
134             _enemyStartHp = (int[])_enemyHp.Clone();\r
135             EnemyResultStatus =\r
136             (from id in (int[])json.api_ship_ke\r
137                 where id != -1\r
138                 select new ShipStatus {Id = id, Spec = _shipInfo.GetSpec(id)}).ToArray();\r
139             _guard = new Record[0];\r
140             _enemyGuardHp = new int[0];\r
141             _enemyGuardStartHp = new int[0];\r
142             if (!json.api_nowhps_combined())\r
143                 return;\r
144             var combined = (int[])json.api_nowhps_combined;\r
145             if (combined[1] != -1) // 味方が連合艦隊\r
146                 _guard = Record.Setup(_shipInfo.GetShipStatuses(1));\r
147             if (combined.Length > 7) // 敵が連合艦隊\r
148             {\r
149                 _enemyGuardHp =\r
150                     ((int[])json.api_nowhps_combined).\r
151                         Skip(7).TakeWhile(hp => hp != -1).ToArray();\r
152                 _enemyGuardStartHp = (int[])_enemyGuardHp.Clone();\r
153             }\r
154         }\r
155 \r
156         private void FlagshipRecovery(ShipStatus flagship)\r
157         {\r
158             switch (_flagshipRecoveryType)\r
159             {\r
160                 case 0:\r
161                     return;\r
162                 case 1:\r
163                     flagship.NowHp = flagship.MaxHp / 2;\r
164                     ConsumeSlotItem(flagship, 42); // ダメコン\r
165                     break;\r
166                 case 2:\r
167                     flagship.NowHp = flagship.MaxHp;\r
168                     ConsumeSlotItem(flagship, 43); // 女神\r
169                     break;\r
170             }\r
171             if (_flagshipRecoveryType != 0)\r
172                 _shipInfo.SetBadlyDamagedShips();\r
173             _flagshipRecoveryType = 0;\r
174         }\r
175 \r
176         private static void ConsumeSlotItem(ShipStatus ship, int id)\r
177         {\r
178             if (ship.SlotEx.Spec.Id == id)\r
179             {\r
180                 ship.SlotEx = new ItemStatus();\r
181                 return;\r
182             }\r
183             for (var i = 0; i < ship.Slot.Length; i++)\r
184             {\r
185                 if (ship.Slot[i].Spec.Id == id)\r
186                 {\r
187                     ship.Slot[i] = new ItemStatus();\r
188                     break;\r
189                 }\r
190             }\r
191         }\r
192 \r
193         public void CleanupResult()\r
194         {\r
195             _friend = null;\r
196         }\r
197 \r
198         private int CheckAirControlLevel(dynamic json)\r
199         {\r
200             if (!json.api_kouku())\r
201                 return -1;\r
202             var stage1 = json.api_kouku.api_stage1;\r
203             if (stage1 == null)\r
204                 return -1;\r
205             if (stage1.api_f_count == 0 && stage1.api_e_count == 0)\r
206                 return -1;\r
207             return (int)stage1.api_disp_seiku;\r
208         }\r
209 \r
210         private string CalcEnemyFighterPower(dynamic json)\r
211         {\r
212             var missing = "";\r
213             var maxEq = ((int[])json.api_ship_ke).Skip(1).SelectMany(id =>\r
214             {\r
215                 var r = _shipInfo.GetSpec(id).MaxEq;\r
216                 if (r != null)\r
217                     return r;\r
218                 missing = "+";\r
219                 return new int[5];\r
220             }).ToArray();\r
221             var equips = ((int[][])json.api_eSlot).SelectMany(x => x);\r
222             return (from slot in equips.Zip(maxEq, (id, max) => new {id, max})\r
223                        let spec = _itemInfo.GetSpecByItemId(slot.id)\r
224                        where spec.CanAirCombat\r
225                        select (int)Floor(spec.AntiAir * Sqrt(slot.max))).DefaultIfEmpty().Sum() + missing;\r
226         }\r
227 \r
228         private void CalcDamage(dynamic json, bool surfaceFleet = false)\r
229         {\r
230             var fc = _guard.Length > 0;\r
231             var ec = _enemyGuardHp.Length > 0;\r
232             var both = fc && ec;\r
233             if (json.api_air_base_attack())\r
234                 CalcAirBaseAttackDamage(json.api_air_base_attack);\r
235             if (json.api_kouku.api_stage3 != null)\r
236                 CalcSimpleDamage(json.api_kouku.api_stage3, _friend, _enemyHp);\r
237             if (json.api_kouku.api_stage3_combined() && json.api_kouku.api_stage3_combined != null)\r
238                 CalcSimpleDamage(json.api_kouku.api_stage3_combined, _guard, _enemyGuardHp);\r
239             if (json.api_kouku2()) // 航空戦2回目\r
240             {\r
241                 if (json.api_kouku2.api_stage3 != null)\r
242                     CalcSimpleDamage(json.api_kouku2.api_stage3, _friend, _enemyHp);\r
243                 if (json.api_kouku2.api_stage3_combined() && json.api_kouku2.api_stage3_combined != null)\r
244                     CalcSimpleDamage(json.api_kouku2.api_stage3_combined, _guard, _enemyGuardHp);\r
245             }\r
246             if (!json.api_opening_atack()) // 航空戦のみ\r
247                 return;\r
248             if (json.api_support_info() && json.api_support_info != null)\r
249                 CalcSupportDamage(json.api_support_info);\r
250             if (json.api_opening_taisen() && json.api_opening_taisen != null)\r
251             {\r
252                 CalcHougekiDamage(json.api_opening_taisen,\r
253                     fc ? _guard : _friend, // 先制対潜攻撃の対象は護衛(たぶん)\r
254                     _enemyHp);\r
255             }\r
256             if (json.api_opening_atack != null)\r
257             {\r
258                 if (both)\r
259                 {\r
260                     CalcSimpleDamage(json.api_opening_atack, _friend, _guard, _enemyHp, _enemyGuardHp);\r
261                 }\r
262                 else\r
263                 {\r
264                     CalcSimpleDamage(json.api_opening_atack,\r
265                         fc ? _guard : _friend, // 雷撃の対象は護衛\r
266                         _enemyHp, _enemyGuardHp);\r
267                 }\r
268             }\r
269             if (json.api_hougeki1() && json.api_hougeki1 != null)\r
270             {\r
271                 if (json.api_hougeki1.api_at_eflag())\r
272                 {\r
273                     CalcCombinedHougekiDamage(json.api_hougeki1, _friend, _guard, _enemyHp, _enemyGuardHp);\r
274                 }\r
275                 else\r
276                 {\r
277                     CalcHougekiDamage(json.api_hougeki1,\r
278                         fc && !surfaceFleet ? _guard : _friend, // 空母機動部隊は一巡目が護衛\r
279                         ec ? _enemyGuardHp : _enemyHp); // 敵連合艦隊は一巡目が護衛\r
280                 }\r
281             }\r
282             if (json.api_hougeki2() && json.api_hougeki2 != null)\r
283             {\r
284                 if (json.api_hougeki2.api_at_eflag())\r
285                     CalcCombinedHougekiDamage(json.api_hougeki2, _friend, _guard, _enemyHp, _enemyGuardHp);\r
286                 else\r
287                     CalcHougekiDamage(json.api_hougeki2, _friend, _enemyHp);\r
288             }\r
289             if (json.api_hougeki3() && json.api_hougeki3 != null)\r
290             {\r
291                 if (json.api_hougeki3.api_at_eflag())\r
292                 {\r
293                     CalcCombinedHougekiDamage(json.api_hougeki3, _friend, _guard, _enemyHp, _enemyGuardHp);\r
294                 }\r
295                 else\r
296                 {\r
297                     CalcHougekiDamage(json.api_hougeki3,\r
298                         fc && surfaceFleet ? _guard : _friend, // 水上打撃部隊は三順目が護衛\r
299                         _enemyHp);\r
300                 }\r
301             }\r
302             if (json.api_raigeki() && json.api_raigeki != null)\r
303             {\r
304                 if (both)\r
305                 {\r
306                     CalcSimpleDamage(json.api_raigeki, _friend, _guard, _enemyHp, _enemyGuardHp);\r
307                 }\r
308                 else\r
309                 {\r
310                     CalcSimpleDamage(json.api_raigeki,\r
311                         fc ? _guard : _friend, // 雷撃の対象は護衛\r
312                         _enemyHp, _enemyGuardHp);\r
313                 }\r
314             }\r
315         }\r
316 \r
317         private void CalcSupportDamage(dynamic json)\r
318         {\r
319             if (json.api_support_hourai != null)\r
320             {\r
321                 CalcSimpleDamage(json.api_support_hourai.api_damage, _enemyHp, _enemyGuardHp);\r
322             }\r
323             else if (json.api_support_airatack != null)\r
324             {\r
325                 var airattack = json.api_support_airatack;\r
326                 if (airattack.api_stage3 != null)\r
327                     CalcSimpleDamage(airattack.api_stage3.api_edam, _enemyHp);\r
328                 if (airattack.api_stage3_combined() && airattack.api_stage3_combined != null)\r
329                     CalcSimpleDamage(airattack.api_stage3_combined.api_edam, _enemyGuardHp);\r
330             }\r
331         }\r
332 \r
333         private void CalcAirBaseAttackDamage(dynamic json)\r
334         {\r
335             foreach (var entry in json)\r
336             {\r
337                 if (!entry.api_stage3() || entry.api_stage3 == null)\r
338                     continue;\r
339                 CalcSimpleDamage(entry.api_stage3.api_edam, _enemyHp);\r
340                 if (entry.api_stage3_combined())\r
341                     CalcSimpleDamage(entry.api_stage3_combined.api_edam, _enemyGuardHp);\r
342             }\r
343         }\r
344 \r
345         private void CalcSimpleDamage(dynamic json, Record[] friend, int[] enemy)\r
346         {\r
347             if (json.api_fdam())\r
348                 CalcSimpleDamage(json.api_fdam, friend);\r
349             if (json.api_edam())\r
350                 CalcSimpleDamage(json.api_edam, enemy);\r
351         }\r
352 \r
353         private void CalcSimpleDamage(dynamic json, Record[] friend, int[] enemy, int[] enemyGuard)\r
354         {\r
355             CalcSimpleDamage(json.api_fdam, friend);\r
356             CalcSimpleDamage(json.api_edam, enemy, enemyGuard);\r
357         }\r
358 \r
359         private void CalcSimpleDamage(dynamic json, Record[] friend, Record[] guard, int[] enemy, int[] enemyGuard)\r
360         {\r
361             CalcSimpleDamage(json.api_fdam, friend, guard);\r
362             CalcSimpleDamage(json.api_edam, enemy, enemyGuard);\r
363         }\r
364 \r
365         private void CalcSimpleDamage(dynamic rawDamage, Record[] friend, Record[] guard)\r
366         {\r
367             var damage = (int[])rawDamage;\r
368             for (var i = 0; i < friend.Length; i++)\r
369                 friend[i].ApplyDamage(damage[i + 1]);\r
370             for (var i = 0; i < guard.Length; i++)\r
371                 guard[i].ApplyDamage(damage[i + 6 + 1]);\r
372         }\r
373 \r
374         private void CalcSimpleDamage(dynamic rawDamage, Record[] friend)\r
375         {\r
376             var damage = (int[])rawDamage;\r
377             for (var i = 0; i < friend.Length; i++)\r
378                 friend[i].ApplyDamage(damage[i + 1]);\r
379         }\r
380 \r
381         private void CalcSimpleDamage(dynamic rawDamage, int[] enemy, int[] enemyGuard)\r
382         {\r
383             var damage = (int[])rawDamage;\r
384             for (var i = 0; i < enemy.Length; i++)\r
385                 enemy[i] -= damage[i + 1];\r
386             for (var i = 0; i < enemyGuard.Length; i++)\r
387                 enemyGuard[i] -= damage[i + 6 + 1];\r
388         }\r
389 \r
390         private void CalcSimpleDamage(dynamic rawDamage, int[] result)\r
391         {\r
392             var damage = (int[])rawDamage;\r
393             for (var i = 0; i < result.Length; i++)\r
394                 result[i] -= damage[i + 1];\r
395         }\r
396 \r
397         private void CalcHougekiDamage(dynamic hougeki, Record[] friend, int[] enemy)\r
398         {\r
399             var targets = ((dynamic[])hougeki.api_df_list).Skip(1).SelectMany(x => (int[])x);\r
400             var damages = ((dynamic[])hougeki.api_damage).Skip(1).SelectMany(x => (int[])x);\r
401             foreach (var hit in targets.Zip(damages, (t, d) => new {t, d}))\r
402             {\r
403                 if (hit.t == -1)\r
404                     continue;\r
405                 if (hit.t <= 6)\r
406                     friend[hit.t - 1].ApplyDamage(hit.d);\r
407                 else\r
408                     enemy[(hit.t - 1) % 6] -= hit.d;\r
409             }\r
410         }\r
411 \r
412         private void CalcCombinedHougekiDamage(dynamic hougeki, Record[] friend, Record[] guard,\r
413             int[] enemy, int[] enemyGuard)\r
414         {\r
415             var targets = ((dynamic[])hougeki.api_df_list).Skip(1).Select(x => (int[])x);\r
416             var damages = ((dynamic[])hougeki.api_damage).Skip(1).Select(x => (int[])x);\r
417             var eflags = ((int[])hougeki.api_at_eflag).Skip(1);\r
418             foreach (var turn in\r
419                 targets.Zip(damages, (t, d) => new {t, d}).\r
420                     Zip(eflags, (td, e) => new {e, td.t, td.d}))\r
421             {\r
422                 foreach (var hit in turn.t.Zip(turn.d, (t, d) => new {t, d}))\r
423                 {\r
424                     if (turn.e == 1)\r
425                     {\r
426                         if (hit.t <= 6)\r
427                             friend[hit.t - 1].ApplyDamage(hit.d);\r
428                         else\r
429                             guard[(hit.t - 1) % 6].ApplyDamage(hit.d);\r
430                     }\r
431                     else\r
432                     {\r
433                         if (hit.t <= 6)\r
434                             enemy[hit.t - 1] -= hit.d;\r
435                         else\r
436                             enemyGuard[(hit.t - 1) % 6] -= hit.d;\r
437                     }\r
438                 }\r
439             }\r
440         }\r
441 \r
442         public void InspectBattleResult(dynamic json)\r
443         {\r
444             ShowResult();\r
445             CleanupResult();\r
446             SetEscapeShips(json);\r
447         }\r
448 \r
449         public void InspectPracticeResult(dynamic json)\r
450         {\r
451             ShowResult(false);\r
452             CleanupResult();\r
453         }\r
454 \r
455         private void ShowResult(bool warnDamagedShip = true)\r
456         {\r
457             if (_friend == null)\r
458                 return;\r
459             var ships = _guard.Length > 0\r
460                 ? _shipInfo.GetShipStatuses(0).Concat(_shipInfo.GetShipStatuses(1)).ToArray()\r
461                 : _shipInfo.GetShipStatuses(_fleet);\r
462             foreach (var entry in ships.Zip(_friend.Concat(_guard), (ship, now) => new {ship, now}))\r
463                 entry.now.UpdateShipStatus(entry.ship);\r
464             if (warnDamagedShip)\r
465                 _shipInfo.SetBadlyDamagedShips();\r
466             else\r
467                 _shipInfo.ClearBadlyDamagedShips();\r
468             SetEnemyResultStatus();\r
469         }\r
470 \r
471         private void SetEnemyResultStatus()\r
472         {\r
473             for (var i = 0; i < EnemyResultStatus.Length; i++)\r
474             {\r
475                 EnemyResultStatus[i].MaxHp = _enemyStartHp[i];\r
476                 EnemyResultStatus[i].NowHp = _enemyHp[i];\r
477             }\r
478         }\r
479 \r
480         public void SetEscapeShips(dynamic json)\r
481         {\r
482             _escapingShips.Clear();\r
483             if (!json.api_escape_flag() || (int)json.api_escape_flag == 0)\r
484                 return;\r
485             var damaged = (int)json.api_escape.api_escape_idx[0] - 1;\r
486             _escapingShips.Add(_shipInfo.GetDeck(damaged / 6)[damaged % 6]);\r
487             var escort = (int)json.api_escape.api_tow_idx[0] - 1;\r
488             _escapingShips.Add(_shipInfo.GetDeck(escort / 6)[escort % 6]);\r
489         }\r
490 \r
491         public void CauseCombinedBattleEscape()\r
492         {\r
493             _shipInfo.SetEscapedShips(_escapingShips);\r
494             _shipInfo.SetBadlyDamagedShips();\r
495         }\r
496 \r
497         private class Record\r
498         {\r
499             private ShipStatus _status;\r
500             public int NowHp => _status.NowHp;\r
501             public bool Escaped => _status.Escaped;\r
502             public ShipStatus.Damage DamageLevel => _status.DamageLevel;\r
503             public int StartHp;\r
504 \r
505             public static Record[] Setup(ShipStatus[] ships) =>\r
506                 (from s in ships select new Record {_status = (ShipStatus)s.Clone(), StartHp = s.NowHp}).ToArray();\r
507 \r
508             public void ApplyDamage(int damage)\r
509             {\r
510                 if (_status.NowHp > damage)\r
511                 {\r
512                     _status.NowHp -= damage;\r
513                     return;\r
514                 }\r
515                 _status.NowHp = 0;\r
516                 foreach (var item in new[] {_status.SlotEx}.Concat(_status.Slot))\r
517                 {\r
518                     if (item.Spec.Id == 42)\r
519                     {\r
520                         _status.NowHp = (int)(_status.MaxHp * 0.2);\r
521                         ConsumeSlotItem(_status, 42);\r
522                         break;\r
523                     }\r
524                     if (item.Spec.Id == 43)\r
525                     {\r
526                         _status.NowHp = _status.MaxHp;\r
527                         ConsumeSlotItem(_status, 43);\r
528                         break;\r
529                     }\r
530                 }\r
531             }\r
532 \r
533             public void UpdateShipStatus(ShipStatus ship)\r
534             {\r
535                 ship.NowHp = NowHp;\r
536                 ship.Slot = _status.Slot;\r
537                 ship.SlotEx = _status.SlotEx;\r
538             }\r
539         }\r
540 \r
541         private BattleResultRank CalcLdAirBattleRank()\r
542         {\r
543             var combined = _friend.Concat(_guard).ToArray();\r
544             var friendNowShips = combined.Count(r => r.NowHp > 0);\r
545             var friendGauge = combined.Sum(r => r.StartHp - r.NowHp);\r
546             var friendSunk = combined.Count(r => r.NowHp == 0);\r
547             var friendGaugeRate = Floor((double)friendGauge / combined.Sum(r => r.StartHp) * 100);\r
548 \r
549             if (friendSunk == 0)\r
550             {\r
551                 if (friendGauge == 0)\r
552                     return BattleResultRank.P;\r
553                 if (friendGaugeRate < 10)\r
554                     return BattleResultRank.A;\r
555                 if (friendGaugeRate < 20)\r
556                     return BattleResultRank.B;\r
557                 if (friendGaugeRate < 50)\r
558                     return BattleResultRank.C;\r
559                 return BattleResultRank.D;\r
560             }\r
561             if (friendSunk < friendNowShips)\r
562                 return BattleResultRank.D;\r
563             return BattleResultRank.E;\r
564         }\r
565 \r
566         private BattleResultRank CalcResultRank()\r
567         {\r
568             var friend = _friend.Concat(_guard).ToArray();\r
569             var enemyHp = _enemyHp.Concat(_enemyGuardHp).ToArray();\r
570             var enemyStartHp = _enemyStartHp.Concat(_enemyGuardStartHp).ToArray();\r
571 \r
572             var friendCount = friend.Length;\r
573             var friendStartHpTotal = 0;\r
574             var friendNowHpTotal = 0;\r
575             var friendSunk = 0;\r
576             foreach (var ship in friend)\r
577             {\r
578                 if (ship.Escaped)\r
579                     continue;\r
580                 friendStartHpTotal += ship.StartHp;\r
581                 friendNowHpTotal += ship.NowHp;\r
582                 if (ship.NowHp == 0)\r
583                     friendSunk++;\r
584             }\r
585             var friendGaugeRate = (int)((double)(friendStartHpTotal - friendNowHpTotal) / friendStartHpTotal * 100);\r
586 \r
587             var enemyCount = enemyHp.Length;\r
588             var enemyStartHpTotal = enemyStartHp.Sum();\r
589             var enemyNowHpTotal = enemyHp.Sum();\r
590             var enemySunk = enemyHp.Count(hp => hp == 0);\r
591             var enemyGaugeRate = (int)((double)(enemyStartHpTotal - enemyNowHpTotal) / enemyStartHpTotal * 100);\r
592 \r
593             if (friendSunk == 0 && enemySunk == enemyCount)\r
594             {\r
595                 if (friendNowHpTotal >= friendStartHpTotal)\r
596                     return BattleResultRank.P;\r
597                 return BattleResultRank.S;\r
598             }\r
599             if (friendSunk == 0 && enemySunk >= (int)(enemyCount * 0.7) && enemyCount > 1)\r
600                 return BattleResultRank.A;\r
601             if (friendSunk < enemySunk && enemyHp[0] == 0)\r
602                 return BattleResultRank.B;\r
603             if (friendCount == 1 && friend[0].DamageLevel == ShipStatus.Damage.Badly)\r
604                 return BattleResultRank.D;\r
605             if (enemyGaugeRate > friendGaugeRate * 2.5)\r
606                 return BattleResultRank.B;\r
607             if (enemyGaugeRate > friendGaugeRate * 0.9)\r
608                 return BattleResultRank.C;\r
609             if (friendCount > 1 && friendCount - 1 == friendSunk)\r
610                 return BattleResultRank.E;\r
611             return BattleResultRank.D;\r
612         }\r
613     }\r
614 }