OSDN Git Service

索敵に航空偵察スコアを表示する
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / Model / Fleet.cs
1 // Copyright (C) 2018 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 // ReSharper disable CompareOfFloatsByEqualityOperator\r
21 \r
22 namespace KancolleSniffer.Model\r
23 {\r
24     public class ChargeStatus\r
25     {\r
26         public bool Empty => FuelRatio > 1.0;\r
27 \r
28         public double FuelRatio { get; set; }\r
29         public int Fuel { get; set; }\r
30 \r
31         public double BullRatio { get; set; }\r
32         public int Bull { get; set; }\r
33 \r
34         private ChargeStatus()\r
35         {\r
36         }\r
37 \r
38         public ChargeStatus(ShipStatus status)\r
39         {\r
40             if (status.Spec.FuelMax == 0)\r
41             {\r
42                 FuelRatio = 2.0;\r
43                 BullRatio = 2.0;\r
44                 return;\r
45             }\r
46             FuelRatio = (double)status.Fuel / status.Spec.FuelMax;\r
47             BullRatio = (double)status.Bull / status.Spec.BullMax;\r
48         }\r
49 \r
50         public void SetState()\r
51         {\r
52             Fuel = CalcChargeState(FuelRatio);\r
53             Bull = CalcChargeState(BullRatio);\r
54         }\r
55 \r
56         public void SetOthersState()\r
57         {\r
58             SetState();\r
59             Fuel += 5;\r
60             Bull += 5;\r
61         }\r
62 \r
63         private static int CalcChargeState(double ratio)\r
64         {\r
65             if (ratio > 1.0)\r
66                 return 0;\r
67             if (ratio == 1.0)\r
68                 return 0;\r
69             if (ratio >= 7.0 / 9)\r
70                 return 1;\r
71             if (ratio >= 3.0 / 9)\r
72                 return 2;\r
73             if (ratio > 0)\r
74                 return 3;\r
75             return 4;\r
76         }\r
77 \r
78         public static ChargeStatus Min(ChargeStatus a, ChargeStatus b)\r
79         {\r
80             return new ChargeStatus\r
81             {\r
82                 FuelRatio = Math.Min(a.FuelRatio, b.FuelRatio),\r
83                 BullRatio = Math.Min(a.BullRatio, b.BullRatio)\r
84             };\r
85         }\r
86     }\r
87 \r
88     public enum FleetState\r
89     {\r
90         Port,\r
91         Mission,\r
92         Sortie,\r
93         Practice\r
94     }\r
95 \r
96     public enum CombinedType\r
97     {\r
98         None,\r
99         Carrier, // 機動\r
100         Surface, // 水上\r
101         Transport // 輸送\r
102     }\r
103 \r
104     public class Fleet\r
105     {\r
106         private readonly ShipInventory _shipInventory;\r
107         private readonly Func<int> _getHqLevel;\r
108         private int[] _deck = Enumerable.Repeat(-1, ShipInfo.MemberCount).ToArray();\r
109         public int Number { get; }\r
110         public FleetState State { get; set; }\r
111         public CombinedType CombinedType { get; set; }\r
112         public IReadOnlyList<ShipStatus> Ships { get; private set; }\r
113         public IReadOnlyList<ShipStatus> ActualShips { get; private set; }\r
114 \r
115         public Fleet(ShipInventory shipInventory, int number, Func<int> getHqLevel)\r
116         {\r
117             _shipInventory = shipInventory;\r
118             Number = number;\r
119             _getHqLevel = getHqLevel;\r
120             Ships = _deck.Select(id => new ShipStatus()).ToArray();\r
121             ActualShips = new ShipStatus[0];\r
122         }\r
123 \r
124         public IReadOnlyList<int> Deck\r
125         {\r
126             get => _deck.ToArray();\r
127             set\r
128             {\r
129                 _deck = value.ToArray();\r
130                 SetDeck();\r
131             }\r
132         }\r
133 \r
134         public void SetDeck()\r
135         {\r
136             foreach (var ship in Ships)\r
137             {\r
138                 if (ship.Fleet != this) // 入れ替え操作で他の艦隊に移動しているときには触らない。\r
139                     continue;\r
140                 ship.Fleet = null;\r
141                 ship.DeckIndex = -1;\r
142             }\r
143             Ships = _deck.Select((id, num) =>\r
144             {\r
145                 var ship = _shipInventory[id];\r
146                 if (ship.Empty)\r
147                     return ship;\r
148                 ship.DeckIndex = num;\r
149                 ship.Fleet = this;\r
150                 return ship;\r
151             }).ToArray();\r
152             ActualShips = Ships.Where(ship => !ship.Empty).ToArray();\r
153         }\r
154 \r
155         public int SetShip(int index, int shipId)\r
156         {\r
157             var prev = _deck[index];\r
158             _deck[index] = shipId;\r
159             SetDeck();\r
160             return prev;\r
161         }\r
162 \r
163         public void WithdrawAccompanyingShips()\r
164         {\r
165             for (var i = 1; i < _deck.Length; i++)\r
166                 _deck[i] = -1;\r
167             SetDeck();\r
168         }\r
169 \r
170         public void WithdrawShip(int index)\r
171         {\r
172             var dst = index;\r
173             for (var src = index + 1; src < _deck.Length; src++)\r
174             {\r
175                 if (_deck[src] != -1)\r
176                     _deck[dst++] = _deck[src];\r
177             }\r
178             for (; dst < _deck.Length; dst++)\r
179                 _deck[dst] = -1;\r
180             SetDeck();\r
181         }\r
182 \r
183         public ChargeStatus ChargeStatus\r
184         {\r
185             get\r
186             {\r
187                 var fs = new ChargeStatus(Ships[0]);\r
188                 var others = (from ship in Ships.Skip(1) select new ChargeStatus(ship)).Aggregate(ChargeStatus.Min);\r
189                 fs.SetState();\r
190                 others.SetOthersState();\r
191                 if (others.Empty)\r
192                     return fs;\r
193                 if (fs.Fuel == 0)\r
194                 {\r
195                     fs.Fuel = others.Fuel;\r
196                     fs.FuelRatio = others.FuelRatio;\r
197                 }\r
198                 if (fs.Bull == 0)\r
199                 {\r
200                     fs.Bull = others.Bull;\r
201                     fs.BullRatio = others.BullRatio;\r
202                 }\r
203                 return fs;\r
204             }\r
205         }\r
206 \r
207         public Range FighterPower\r
208             => ActualShips.Where(ship => !ship.Escaped).SelectMany(ship =>\r
209                     ship.Slot.Zip(ship.OnSlot, (slot, onSlot) => slot.CalcFighterPower(onSlot)))\r
210                 .Aggregate(new Range(0, 0), (prev, cur) => prev + cur);\r
211 \r
212         public double ContactTriggerRate\r
213             => ActualShips.Where(ship => !ship.Escaped).SelectMany(ship =>\r
214                 ship.Slot.Zip(ship.OnSlot, (slot, onSlot) =>\r
215                     slot.Spec.ContactTriggerRate * slot.Spec.LoS * Sqrt(onSlot))).Sum();\r
216 \r
217         public double GetLineOfSights(int factor)\r
218         {\r
219             var result = 0.0;\r
220             var emptyBonus = 6;\r
221             foreach (var s in ActualShips.Where(s => !s.Escaped))\r
222             {\r
223                 emptyBonus--;\r
224                 var itemLoS = 0;\r
225                 foreach (var item in s.AllSlot)\r
226                 {\r
227                     var spec = item.Spec;\r
228                     itemLoS += spec.LoS;\r
229                     result += (spec.LoS + item.LoSLevelBonus) * spec.LoSScaleFactor * factor;\r
230                 }\r
231                 result += Sqrt(s.LoS - itemLoS);\r
232             }\r
233             return result > 0 ? result - Ceiling(_getHqLevel() * 0.4) + emptyBonus * 2 : 0.0;\r
234         }\r
235 \r
236         public double AirReconScore =>\r
237             ActualShips.SelectMany(ship =>\r
238                 ship.OnSlot.Zip(ship.Slot, (slot, item) => item.Spec.CalcAirReconScore(slot))).Sum();\r
239 \r
240         public double DaihatsuBonus\r
241         {\r
242             get\r
243             {\r
244                 // ReSharper disable IdentifierTypo\r
245                 var tokudaiBonus = new[,]\r
246                 {\r
247                     {0.00, 0.00, 0.00, 0.00, 0.00},\r
248                     {0.02, 0.02, 0.02, 0.02, 0.02},\r
249                     {0.04, 0.04, 0.04, 0.04, 0.04},\r
250                     {0.05, 0.05, 0.052, 0.054, 0.054},\r
251                     {0.054, 0.056, 0.058, 0.059, 0.06}\r
252                 };\r
253                 var daihatsu = 0;\r
254                 var tokudai = 0;\r
255                 var bonus = 0.0;\r
256                 var level = 0;\r
257                 var sum = 0;\r
258                 // ReSharper restore IdentifierTypo\r
259                 foreach (var ship in Ships)\r
260                 {\r
261                     if (ship.Name == "鬼怒改二")\r
262                         bonus += 0.05;\r
263                     foreach (var item in ship.Slot)\r
264                     {\r
265                         switch (item.Spec.Name)\r
266                         {\r
267                             case "大発動艇":\r
268                                 level += item.Level;\r
269                                 sum++;\r
270                                 daihatsu++;\r
271                                 bonus += 0.05;\r
272                                 break;\r
273                             case "特大発動艇":\r
274                                 level += item.Level;\r
275                                 sum++;\r
276                                 tokudai++;\r
277                                 bonus += 0.05;\r
278                                 break;\r
279                             case "大発動艇(八九式中戦車&陸戦隊)":\r
280                                 level += item.Level;\r
281                                 sum++;\r
282                                 bonus += 0.02;\r
283                                 break;\r
284                             case "特二式内火艇":\r
285                                 level += item.Level;\r
286                                 sum++;\r
287                                 bonus += 0.01;\r
288                                 break;\r
289                         }\r
290                     }\r
291                 }\r
292                 var levelAverage = sum == 0 ? 0.0 : (double)level / sum;\r
293                 bonus = Min(bonus, 0.2);\r
294                 var result = bonus + 0.01 * bonus * levelAverage + tokudaiBonus[Min(tokudai, 4), Min(daihatsu, 4)];\r
295                 return Floor(result * 1000) / 10;\r
296             }\r
297         }\r
298 \r
299         public double TransportPoint => Ships.Where(ship => !ship.Escaped).Sum(ship => ship.TransportPoint);\r
300 \r
301         public int CombinedFirepowerBonus\r
302         {\r
303             get\r
304             {\r
305                 switch (CombinedType)\r
306                 {\r
307                     case CombinedType.None:\r
308                         return 0;\r
309                     case CombinedType.Carrier:\r
310                         return Number == 0 ? 2 : 10;\r
311                     case CombinedType.Surface:\r
312                         return Number == 0 ? 10 : -5;\r
313                     case CombinedType.Transport:\r
314                         return Number == 0 ? -5 : 10;\r
315                     default:\r
316                         return 0;\r
317                 }\r
318             }\r
319         }\r
320 \r
321         public int CombinedTorpedoPenalty => CombinedType != 0 && Number == 1 ? -5 : 0;\r
322 \r
323         public double NightContactTriggerRate => (1.0 - NightContactFailRate) * 100;\r
324 \r
325         private double NightContactFailRate => Ships.Aggregate(1.0,\r
326             (perFleet, ship) => ship.OnSlot.Where((onSlot, i) => onSlot > 0 && ship.Slot[i].Spec.IsNightRecon)\r
327                 .Aggregate(perFleet, (perShip, _) => perShip * NightContactFailRateOne(ship)));\r
328 \r
329         private static double NightContactFailRateOne(ShipStatus ship) => 1.0 - Floor(Sqrt(ship.Level * 3)) * 4 / 100.0;\r
330 \r
331         public string MissionParameter\r
332         {\r
333             get\r
334             {\r
335                 var result = new List<string>();\r
336                 var kira = Ships.Count(ship => ship.Cond > 49);\r
337                 if (kira > 0)\r
338                 {\r
339                     var min = Ships.Where(ship => ship.Cond > 49).Min(ship => ship.Cond);\r
340                     var mark = Ships[0].Cond > 49 ? "+" : "";\r
341                     result.Add($"キラ{kira}{mark}⪰{min}");\r
342                 }\r
343                 var drums = Ships.SelectMany(ship => ship.Slot).Count(item => item.Spec.IsDrum);\r
344                 var drumShips = Ships.Count(ship => ship.Slot.Any(item => item.Spec.IsDrum));\r
345                 if (drums > 0)\r
346                     result.Add($"ド{drums}({drumShips}隻)");\r
347                 if (DaihatsuBonus > 0)\r
348                     result.Add($"ダ{DaihatsuBonus:f1}%");\r
349                 return string.Join(" ", result);\r
350             }\r
351         }\r
352     }\r
353 }