OSDN Git Service

c083ebecbcc06c657d6ef8dde114e6de6c874ad1
[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;\r
16 using System.Collections.Generic;\r
17 using System.Linq;\r
18 using static System.Math;\r
19 \r
20 namespace KancolleSniffer\r
21 {\r
22     public enum BattleResultRank\r
23     {\r
24         P,\r
25         S,\r
26         A,\r
27         B,\r
28         C,\r
29         D,\r
30         E\r
31     }\r
32 \r
33     public enum BattleState\r
34     {\r
35         None,\r
36         Day,\r
37         Night,\r
38         Result,\r
39         Unknown\r
40     }\r
41 \r
42     public class EnemyFighterPower\r
43     {\r
44         public bool HasUnknown { get; set; }\r
45         public string UnknownMark => HasUnknown ? "+" : "";\r
46         public int AirCombat { get; set; }\r
47         public int Interception { get; set; }\r
48     }\r
49 \r
50     public class BattleInfo\r
51     {\r
52         private readonly ShipInfo _shipInfo;\r
53         private readonly ItemInfo _itemInfo;\r
54         private int _fleet;\r
55         private Record[] _friend;\r
56         private Record[] _guard;\r
57         private Record[] _enemy;\r
58         private Record[] _enemyGuard;\r
59         private readonly List<int> _escapingShips = new List<int>();\r
60         private bool _lastCell;\r
61 \r
62         public BattleState BattleState { get; set; }\r
63         public string Formation { get; private set; }\r
64         public EnemyFighterPower EnemyFighterPower { get; private set; }\r
65         public int AirControlLevel { get; private set; }\r
66         public BattleResultRank ResultRank { get; private set; }\r
67         public RankPair DisplayedResultRank { get; } = new RankPair();\r
68         public BattleResult Result { get; set; }\r
69         public bool EnemyIsCombined => _enemyGuard.Length > 0;\r
70         public List<AirBattleResult> AirBattleResults { get; } = new List<AirBattleResult>();\r
71 \r
72         public class RankPair\r
73         {\r
74             public char Assumed { get; set; }\r
75             public char Actual { get; set; }\r
76             public bool IsError => Assumed != Actual;\r
77         }\r
78 \r
79         public class BattleResult\r
80         {\r
81             public class Combined\r
82             {\r
83                 public ShipStatus[] Main { get; set; }\r
84                 public ShipStatus[] Guard { get; set; }\r
85             }\r
86 \r
87             public Combined Friend { get; set; }\r
88             public Combined Enemy { get; set; }\r
89         }\r
90 \r
91         public BattleInfo(ShipInfo shipInfo, ItemInfo itemInfo)\r
92         {\r
93             _shipInfo = shipInfo;\r
94             _itemInfo = itemInfo;\r
95         }\r
96 \r
97         public void InspectBattle(string url, string request, dynamic json)\r
98         {\r
99             Formation = FormationName(json);\r
100             AirControlLevel = CheckAirControlLevel(json);\r
101             ShowResult(false); // 昼戦の結果を夜戦のときに表示する\r
102             SetupResult(request, json);\r
103             EnemyFighterPower = CalcEnemyFighterPower(json);\r
104             BattleState = IsNightBattle(json) ? BattleState.Night : BattleState.Day;\r
105             CalcDamage(json);\r
106             ResultRank = url.EndsWith("ld_airbattle") ? CalcLdAirBattleRank() : CalcResultRank();\r
107             SetResult();\r
108         }\r
109 \r
110         private bool IsNightBattle(dynamic json) => json.api_hougeki();\r
111 \r
112         public static int DeckId(dynamic json)\r
113         {\r
114             if (json.api_dock_id()) // 昼戦はtypoしている\r
115                 return (int)json.api_dock_id - 1;\r
116             if (json.api_deck_id is string) // 通常の夜戦と連合艦隊(味方のみ)では文字列\r
117                 return int.Parse(json.api_deck_id) - 1;\r
118             return (int)json.api_deck_id - 1;\r
119         }\r
120 \r
121         private string FormationName(dynamic json)\r
122         {\r
123             if (!json.api_formation()) // 演習の夜戦\r
124                 return "";\r
125             switch ((int)json.api_formation[2])\r
126             {\r
127                 case 1:\r
128                     return "同航戦";\r
129                 case 2:\r
130                     return "反航戦";\r
131                 case 3:\r
132                     return "T字有利";\r
133                 case 4:\r
134                     return "T字不利";\r
135             }\r
136             return "";\r
137         }\r
138 \r
139         private void SetupResult(string request, dynamic json)\r
140         {\r
141             if (_friend != null)\r
142                 return;\r
143             _shipInfo.SaveBattleStartStatus();\r
144             _fleet = DeckId(json);\r
145             var fstats = _shipInfo.GetShipStatuses(_fleet);\r
146             FlagshipRecovery(request, fstats[0]);\r
147             _friend = Record.Setup(fstats);\r
148             _guard = json.api_f_nowhps_combined() ? Record.Setup(_shipInfo.GetShipStatuses(1)) : new Record[0];\r
149             _enemy = Record.Setup((int[])json.api_e_nowhps,\r
150                 ((int[])json.api_ship_ke).Select(_shipInfo.GetSpec).ToArray(),\r
151                 ((int[][])json.api_eSlot).Select(slot => slot.Select(_itemInfo.GetSpecByItemId).ToArray()).ToArray());\r
152             _enemyGuard = json.api_ship_ke_combined()\r
153                 ? Record.Setup((int[])json.api_e_nowhps_combined,\r
154                     ((int[])json.api_ship_ke_combined).Select(_shipInfo.GetSpec).ToArray(),\r
155                     ((int[][])json.api_eSlot).Select(slot => slot.Select(_itemInfo.GetSpecByItemId).ToArray())\r
156                     .ToArray())\r
157                 : new Record[0];\r
158         }\r
159 \r
160         private void FlagshipRecovery(string request, ShipStatus flagship)\r
161         {\r
162             var type = int.Parse(HttpUtility.ParseQueryString(request)["api_recovery_type"] ?? "0");\r
163             switch (type)\r
164             {\r
165                 case 0:\r
166                     return;\r
167                 case 1:\r
168                     flagship.NowHp = flagship.MaxHp / 2;\r
169                     ConsumeSlotItem(flagship, 42); // ダメコン\r
170                     break;\r
171                 case 2:\r
172                     flagship.NowHp = flagship.MaxHp;\r
173                     ConsumeSlotItem(flagship, 43); // 女神\r
174                     break;\r
175             }\r
176             if (type != 0)\r
177                 _shipInfo.SetBadlyDamagedShips();\r
178         }\r
179 \r
180         private static void ConsumeSlotItem(ShipStatus ship, int id)\r
181         {\r
182             if (ship.SlotEx.Spec.Id == id)\r
183             {\r
184                 ship.SlotEx = new ItemStatus();\r
185                 return;\r
186             }\r
187             for (var i = 0; i < ship.Slot.Length; i++)\r
188             {\r
189                 if (ship.Slot[i].Spec.Id == id)\r
190                 {\r
191                     ship.Slot[i] = new ItemStatus();\r
192                     break;\r
193                 }\r
194             }\r
195         }\r
196 \r
197         public void CleanupResult()\r
198         {\r
199             _friend = null;\r
200             _lastCell = false;\r
201         }\r
202 \r
203         private int CheckAirControlLevel(dynamic json)\r
204         {\r
205             if (!json.api_kouku())\r
206                 return -1;\r
207             var stage1 = json.api_kouku.api_stage1;\r
208             if (stage1 == null)\r
209                 return -1;\r
210             if (stage1.api_f_count == 0 && stage1.api_e_count == 0)\r
211                 return -1;\r
212             return (int)stage1.api_disp_seiku;\r
213         }\r
214 \r
215         private EnemyFighterPower CalcEnemyFighterPower(dynamic json)\r
216         {\r
217             var result = new EnemyFighterPower();\r
218             var ships = (int[])json.api_ship_ke;\r
219             if (json.api_ship_ke_combined() && _guard.Length > 0)\r
220                 ships = ships.Concat((int[])json.api_ship_ke_combined).ToArray();\r
221             var maxEq = ships.SelectMany(id =>\r
222             {\r
223                 var r = _shipInfo.GetSpec(id).MaxEq;\r
224                 if (r != null)\r
225                     return r;\r
226                 result.HasUnknown = true;\r
227                 return new int[5];\r
228             });\r
229             var equips = ((int[][])json.api_eSlot).SelectMany(x => x);\r
230             if (json.api_eSlot_combined() && _guard.Length > 0)\r
231                 equips = equips.Concat(((int[][])json.api_eSlot_combined).SelectMany(x => x));\r
232             foreach (var entry in from slot in equips.Zip(maxEq, (id, max) => new {id, max})\r
233                 let spec = _itemInfo.GetSpecByItemId(slot.id)\r
234                 let perSlot = (int)Floor(spec.AntiAir * Sqrt(slot.max))\r
235                 select new {spec, perSlot})\r
236             {\r
237                 if (entry.spec.CanAirCombat)\r
238                     result.AirCombat += entry.perSlot;\r
239                 if (entry.spec.IsAircraft)\r
240                     result.Interception += entry.perSlot;\r
241             }\r
242             return result;\r
243         }\r
244 \r
245         private enum CombatType\r
246         {\r
247             AtOnce,\r
248             ByTurn,\r
249             Support,\r
250             Aircraft,\r
251             AirBase\r
252         }\r
253 \r
254         private class Phase\r
255         {\r
256             public string Api { get; }\r
257             public CombatType Type { get; }\r
258             public string Name { get; }\r
259 \r
260             public Phase(string api, CombatType type, string name = "")\r
261             {\r
262                 Api = api;\r
263                 Type = type;\r
264                 Name = name;\r
265             }\r
266         }\r
267 \r
268         private void CalcDamage(dynamic json)\r
269         {\r
270             AirBattleResults.Clear();\r
271             var phases = new[]\r
272             {\r
273                 new Phase("air_base_injection", CombatType.Aircraft, "AB噴式"),\r
274                 new Phase("injection_kouku", CombatType.Aircraft, "噴式"),\r
275                 new Phase("air_base_attack", CombatType.AirBase),\r
276                 new Phase("n_support_info", CombatType.Support),\r
277                 new Phase("n_hougeki1", CombatType.ByTurn),\r
278                 new Phase("n_hougeki2", CombatType.ByTurn),\r
279                 new Phase("kouku", CombatType.Aircraft, "航空戦"),\r
280                 new Phase("kouku2", CombatType.Aircraft, "航空戦2"),\r
281                 new Phase("support_info", CombatType.Support),\r
282                 new Phase("opening_taisen", CombatType.ByTurn),\r
283                 new Phase("opening_atack", CombatType.AtOnce),\r
284                 new Phase("hougeki", CombatType.ByTurn),\r
285                 new Phase("hougeki1", CombatType.ByTurn),\r
286                 new Phase("hougeki2", CombatType.ByTurn),\r
287                 new Phase("hougeki3", CombatType.ByTurn),\r
288                 new Phase("raigeki", CombatType.AtOnce)\r
289             };\r
290             foreach (var phase in phases)\r
291                 CalcDamageByType(json, phase);\r
292         }\r
293 \r
294         private void CalcDamageByType(dynamic json, Phase phase)\r
295         {\r
296             var api = "api_" + phase.Api;\r
297             if (!json.IsDefined(api) || json[api] == null)\r
298                 return;\r
299             switch (phase.Type)\r
300             {\r
301                 case CombatType.AtOnce:\r
302                     CalcDamageAtOnce(json[api]);\r
303                     break;\r
304                 case CombatType.ByTurn:\r
305                     CalcDamageByTurn(json[api]);\r
306                     break;\r
307                 case CombatType.Support:\r
308                     CalcSupportDamage(json[api]);\r
309                     break;\r
310                 case CombatType.Aircraft:\r
311                     AddAirBattleResult(json[api], phase.Name);\r
312                     CalcKoukuDamage(json[api]);\r
313                     break;\r
314                 case CombatType.AirBase:\r
315                     CalcAirBaseAttackDamage(json[api]);\r
316                     break;\r
317             }\r
318         }\r
319 \r
320         private void CalcSupportDamage(dynamic json)\r
321         {\r
322             if (json.api_support_hourai != null)\r
323             {\r
324                 CalcRawDamageAtOnce(json.api_support_hourai.api_damage, _enemy, _enemyGuard);\r
325             }\r
326             else if (json.api_support_airatack != null)\r
327             {\r
328                 CalcRawDamageAtOnce(json.api_support_airatack.api_stage3.api_edam, _enemy, _enemyGuard);\r
329             }\r
330         }\r
331 \r
332         private void CalcAirBaseAttackDamage(dynamic json)\r
333         {\r
334             var i = 1;\r
335             foreach (var entry in json)\r
336             {\r
337                 AddAirBattleResult(entry, "基地" + i++);\r
338                 CalcKoukuDamage(entry);\r
339             }\r
340         }\r
341 \r
342         private void AddAirBattleResult(dynamic json, string phaseName)\r
343         {\r
344             var stage1 = json.api_stage1;\r
345             if (stage1 == null || (stage1.api_f_count == 0 && stage1.api_e_count == 0))\r
346                 return;\r
347             AirBattleResults.Add(new AirBattleResult\r
348             {\r
349                 PhaseName = phaseName,\r
350                 AirControlLevel = json.api_stage1.api_disp_seiku() ? (int)json.api_stage1.api_disp_seiku : 0,\r
351                 Stage1 = new AirBattleResult.StageResult\r
352                 {\r
353                     FriendCount = (int)json.api_stage1.api_f_count,\r
354                     FriendLost = (int)json.api_stage1.api_f_lostcount,\r
355                     EnemyCount = (int)json.api_stage1.api_e_count,\r
356                     EnemyLost = (int)json.api_stage1.api_e_lostcount\r
357                 },\r
358                 Stage2 = json.api_stage2 == null\r
359                     ? new AirBattleResult.StageResult\r
360                     {\r
361                         FriendCount = 0,\r
362                         FriendLost = 0,\r
363                         EnemyCount = 0,\r
364                         EnemyLost = 0\r
365                     }\r
366                     : new AirBattleResult.StageResult\r
367                     {\r
368                         FriendCount = (int)json.api_stage2.api_f_count,\r
369                         FriendLost = (int)json.api_stage2.api_f_lostcount,\r
370                         EnemyCount = (int)json.api_stage2.api_e_count,\r
371                         EnemyLost = (int)json.api_stage2.api_e_lostcount\r
372                     }\r
373             });\r
374         }\r
375 \r
376         private void CalcKoukuDamage(dynamic json)\r
377         {\r
378             if (json.api_stage3() && json.api_stage3 != null)\r
379                 CalcDamageAtOnce(json.api_stage3, _friend, _enemy);\r
380             if (json.api_stage3_combined() && json.api_stage3_combined != null)\r
381                 CalcDamageAtOnce(json.api_stage3_combined, _guard, _enemyGuard);\r
382         }\r
383 \r
384         private void CalcDamageAtOnce(dynamic json)\r
385         {\r
386             CalcDamageAtOnce(json, _friend, _guard, _enemy, _enemyGuard);\r
387         }\r
388 \r
389         private void CalcDamageAtOnce(dynamic json, Record[] friend, Record[] enemy)\r
390         {\r
391             CalcDamageAtOnce(json, friend, null, enemy, null);\r
392         }\r
393 \r
394         private void CalcDamageAtOnce(dynamic json, Record[] friend, Record[] guard, Record[] enemy, Record[] enemyGuard)\r
395         {\r
396             if (json.api_fdam() && json.api_fdam != null)\r
397                 CalcRawDamageAtOnce(json.api_fdam, friend, guard);\r
398             if (json.api_edam() && json.api_edam != null)\r
399                 CalcRawDamageAtOnce(json.api_edam, enemy, enemyGuard);\r
400         }\r
401 \r
402         private void CalcRawDamageAtOnce(dynamic rawDamage, Record[] friend, Record[] guard = null)\r
403         {\r
404             var damage = (int[])rawDamage;\r
405             for (var i = 0; i < friend.Length; i++)\r
406                 friend[i].ApplyDamage(damage[i]);\r
407             if (guard == null)\r
408                 return;\r
409             for (var i = 0; i < guard.Length; i++)\r
410                 guard[i].ApplyDamage(damage[i + 6]);\r
411         }\r
412 \r
413         private void CalcDamageByTurn(dynamic json)\r
414         {\r
415             if (!(json.api_df_list() && json.api_df_list != null &&\r
416                   json.api_damage() && json.api_damage != null &&\r
417                   json.api_at_eflag() && json.api_at_eflag != null))\r
418                 return;\r
419 \r
420             var targets = (int[][])json.api_df_list;\r
421             var damages = (int[][])json.api_damage;\r
422             var eflags = (int[])json.api_at_eflag;\r
423             var records = new[] {new Record[12], new Record[12]};\r
424             Array.Copy(_friend, records[1], _friend.Length);\r
425             Array.Copy(_guard, 0, records[1], 6, _guard.Length);\r
426             Array.Copy(_enemy, records[0], _enemy.Length);\r
427             Array.Copy(_enemyGuard, 0, records[0], 6, _enemyGuard.Length);\r
428             for (var i = 0; i < eflags.Length; i++)\r
429             {\r
430                 // 一度に複数の目標を狙う攻撃はないものと仮定する\r
431                 var hit = new {t = targets[i][0], d = damages[i].Sum(d => d >= 0 ? d : 0)};\r
432                 if (hit.t == -1)\r
433                     continue;\r
434                 records[eflags[i]][hit.t].ApplyDamage(hit.d);\r
435             }\r
436         }\r
437 \r
438         public void InspectMapStart(dynamic json)\r
439         {\r
440             InspectMapNext(json);\r
441         }\r
442 \r
443         public void InspectMapNext(dynamic json)\r
444         {\r
445             _lastCell = (int)json.api_next == 0;\r
446         }\r
447 \r
448         public void InspectBattleResult(dynamic json)\r
449         {\r
450             BattleState = BattleState.Result;\r
451             ShowResult(!_lastCell);\r
452             _shipInfo.SaveBattleResult();\r
453             VerifyResultRank(json);\r
454             CleanupResult();\r
455             SetEscapeShips(json);\r
456         }\r
457 \r
458 \r
459         private void VerifyResultRank(dynamic json)\r
460         {\r
461             if (_friend == null)\r
462                 return;\r
463             if (!json.api_win_rank())\r
464                 return;\r
465             var assumed = "PSABCDE"[(int)ResultRank];\r
466             if (assumed == 'P')\r
467                 assumed = 'S';\r
468             var actual = ((string)json.api_win_rank)[0];\r
469             DisplayedResultRank.Assumed = assumed;\r
470             DisplayedResultRank.Actual = actual;\r
471         }\r
472 \r
473         public void InspectPracticeResult(dynamic json)\r
474         {\r
475             BattleState = BattleState.Result;\r
476             ShowResult(false);\r
477             CleanupResult();\r
478         }\r
479 \r
480         private void ShowResult(bool warnDamagedShip = true)\r
481         {\r
482             if (_friend == null)\r
483                 return;\r
484             var ships = _guard.Length > 0\r
485                 ? _shipInfo.GetShipStatuses(0).Concat(_shipInfo.GetShipStatuses(1)).ToArray()\r
486                 : _shipInfo.GetShipStatuses(_fleet);\r
487             foreach (var entry in ships.Zip(_friend.Concat(_guard), (ship, now) => new {ship, now}))\r
488                 entry.now.UpdateShipStatus(entry.ship);\r
489             if (warnDamagedShip)\r
490                 _shipInfo.SetBadlyDamagedShips();\r
491             else\r
492                 _shipInfo.ClearBadlyDamagedShips();\r
493         }\r
494 \r
495         private void SetResult()\r
496         {\r
497             Result = new BattleResult\r
498             {\r
499                 Friend = new BattleResult.Combined\r
500                 {\r
501                     Main = _friend.Select(r => r.SnapShot).ToArray(),\r
502                     Guard = _guard.Select(r => r.SnapShot).ToArray()\r
503                 },\r
504                 Enemy = new BattleResult.Combined\r
505                 {\r
506                     Main = _enemy.Select(r => r.SnapShot).ToArray(),\r
507                     Guard = _enemyGuard.Select(r => r.SnapShot).ToArray()\r
508                 }\r
509             };\r
510         }\r
511 \r
512         public void SetEscapeShips(dynamic json)\r
513         {\r
514             _escapingShips.Clear();\r
515             if (!json.api_escape_flag() || (int)json.api_escape_flag == 0)\r
516                 return;\r
517             var damaged = (int)json.api_escape.api_escape_idx[0] - 1;\r
518             if (json.api_escape.api_tow_idx())\r
519             {\r
520                 _escapingShips.Add(_shipInfo.GetDeck(damaged / 6)[damaged % 6]);\r
521                 var escort = (int)json.api_escape.api_tow_idx[0] - 1;\r
522                 _escapingShips.Add(_shipInfo.GetDeck(escort / 6)[escort % 6]);\r
523             }\r
524             else\r
525             {\r
526                 _escapingShips.Add(_shipInfo.GetDeck(2)[damaged]);\r
527             }\r
528         }\r
529 \r
530         public void CauseEscape()\r
531         {\r
532             _shipInfo.SetEscapedShips(_escapingShips);\r
533             _shipInfo.SetBadlyDamagedShips();\r
534         }\r
535 \r
536         private class Record\r
537         {\r
538             private ShipStatus _status;\r
539             public ShipStatus SnapShot => (ShipStatus)_status.Clone();\r
540             public int NowHp => _status.NowHp;\r
541             public bool Escaped => _status.Escaped;\r
542             public ShipStatus.Damage DamageLevel => _status.DamageLevel;\r
543             public int StartHp;\r
544 \r
545             public static Record[] Setup(ShipStatus[] ships) =>\r
546                 (from s in ships select new Record {_status = (ShipStatus)s.Clone(), StartHp = s.NowHp}).ToArray();\r
547 \r
548             public static Record[] Setup(int[] nowhps,  ShipSpec[] ships, ItemSpec[][] slots)\r
549             {\r
550                 return Enumerable.Range(0, nowhps.Length).Select(i =>\r
551                     new Record\r
552                     {\r
553                         StartHp = nowhps[i],\r
554                         _status = new ShipStatus\r
555                         {\r
556                             Id = ships[i].Id,\r
557                             NowHp = nowhps[i],\r
558                             MaxHp = nowhps[i],\r
559                             Spec = ships[i],\r
560                             Slot = slots[i].Select(spec => new ItemStatus{Id = spec.Id, Spec = spec}).ToArray(),\r
561                             SlotEx = new ItemStatus(0)\r
562                         }\r
563                     }).ToArray();\r
564             }\r
565 \r
566             public void ApplyDamage(int damage)\r
567             {\r
568                 if (_status.NowHp > damage)\r
569                 {\r
570                     _status.NowHp -= damage;\r
571                     return;\r
572                 }\r
573                 _status.NowHp = 0;\r
574                 foreach (var item in _status.AllSlot)\r
575                 {\r
576                     if (item.Spec.Id == 42)\r
577                     {\r
578                         _status.NowHp = (int)(_status.MaxHp * 0.2);\r
579                         ConsumeSlotItem(_status, 42);\r
580                         break;\r
581                     }\r
582                     if (item.Spec.Id == 43)\r
583                     {\r
584                         _status.NowHp = _status.MaxHp;\r
585                         ConsumeSlotItem(_status, 43);\r
586                         break;\r
587                     }\r
588                 }\r
589             }\r
590 \r
591             public void UpdateShipStatus(ShipStatus ship)\r
592             {\r
593                 ship.NowHp = NowHp;\r
594                 ship.Slot = _status.Slot;\r
595                 ship.SlotEx = _status.SlotEx;\r
596             }\r
597         }\r
598 \r
599         private BattleResultRank CalcLdAirBattleRank()\r
600         {\r
601             var combined = _friend.Concat(_guard).ToArray();\r
602             var friendNowShips = combined.Count(r => r.NowHp > 0);\r
603             var friendGauge = combined.Sum(r => r.StartHp - r.NowHp);\r
604             var friendSunk = combined.Count(r => r.NowHp == 0);\r
605             var friendGaugeRate = Floor((double)friendGauge / combined.Sum(r => r.StartHp) * 100);\r
606 \r
607             if (friendSunk == 0)\r
608             {\r
609                 if (friendGauge == 0)\r
610                     return BattleResultRank.P;\r
611                 if (friendGaugeRate < 10)\r
612                     return BattleResultRank.A;\r
613                 if (friendGaugeRate < 20)\r
614                     return BattleResultRank.B;\r
615                 if (friendGaugeRate < 50)\r
616                     return BattleResultRank.C;\r
617                 return BattleResultRank.D;\r
618             }\r
619             if (friendSunk < friendNowShips)\r
620                 return BattleResultRank.D;\r
621             return BattleResultRank.E;\r
622         }\r
623 \r
624         private BattleResultRank CalcResultRank()\r
625         {\r
626             var friend = _friend.Concat(_guard).ToArray();\r
627             var enemy = _enemy.Concat(_enemyGuard).ToArray();\r
628 \r
629             var friendCount = friend.Length;\r
630             var friendStartHpTotal = 0;\r
631             var friendNowHpTotal = 0;\r
632             var friendSunk = 0;\r
633             foreach (var ship in friend)\r
634             {\r
635                 if (ship.Escaped)\r
636                     continue;\r
637                 friendStartHpTotal += ship.StartHp;\r
638                 friendNowHpTotal += ship.NowHp;\r
639                 if (ship.NowHp == 0)\r
640                     friendSunk++;\r
641             }\r
642             var friendGaugeRate = (int)((double)(friendStartHpTotal - friendNowHpTotal) / friendStartHpTotal * 100);\r
643 \r
644             var enemyCount = enemy.Length;\r
645             var enemyStartHpTotal = enemy.Sum(r => r.StartHp);\r
646             var enemyNowHpTotal = enemy.Sum(r => r.NowHp);\r
647             var enemySunk = enemy.Count(r => r.NowHp == 0);\r
648             var enemyGaugeRate = (int)((double)(enemyStartHpTotal - enemyNowHpTotal) / enemyStartHpTotal * 100);\r
649 \r
650             if (friendSunk == 0 && enemySunk == enemyCount)\r
651             {\r
652                 if (friendNowHpTotal >= friendStartHpTotal)\r
653                     return BattleResultRank.P;\r
654                 return BattleResultRank.S;\r
655             }\r
656             if (friendSunk == 0 && enemySunk >= (int)(enemyCount * 0.7) && enemyCount > 1)\r
657                 return BattleResultRank.A;\r
658             if (friendSunk < enemySunk && enemy[0].NowHp == 0)\r
659                 return BattleResultRank.B;\r
660             if (friendCount == 1 && friend[0].DamageLevel == ShipStatus.Damage.Badly)\r
661                 return BattleResultRank.D;\r
662             if (enemyGaugeRate > friendGaugeRate * 2.5)\r
663                 return BattleResultRank.B;\r
664             if (enemyGaugeRate > friendGaugeRate * 0.9)\r
665                 return BattleResultRank.C;\r
666             if (friendCount > 1 && friendCount - 1 == friendSunk)\r
667                 return BattleResultRank.E;\r
668             return BattleResultRank.D;\r
669         }\r
670 \r
671         /// <summary>\r
672         /// テスト専用\r
673         /// </summary>\r
674         public void InjectEnemyResultStatus(ShipStatus[] enemy, ShipStatus[] guard)\r
675         {\r
676             Result = new BattleResult {Enemy = new BattleResult.Combined {Main = enemy, Guard = guard}};\r
677         }\r
678     }\r
679 }