/// <summary> /// 打撃のスイングデータを取得する /// </summary> /// <param name="actualBallData">投球データ(選球結果含む)</param> /// <returns></returns> public BattingSwingData GetSwingData(PitchingBallData actualBallData) { List<BattingMission> missions = new List<BattingMission>(); if (GameData.RequestedBattingMissions.Count > 0) { // ユーザ指定の作戦があれば実行 missions.AddRange(GameData.RequestedBattingMissions); } else { // 「おまかせ」か高速試合モードか敵のAIの場合は、智力依存で作戦を登録する AddMissions(missions); } // どちらかの作戦しか実行できないものも削除しない(優先度高い方が採用される) // 基本作戦の「ストライクを振る」「クサイ所はカット」の作戦を追加 missions.Add(new BattingMission(BattingMissionKind.SwingStrike)); missions.Add(new BattingMission(BattingMissionKind.CutScantBall)); // スイングデータの作成開始 BattingSwingData swingData = new BattingSwingData(); // 優先順位の低い順にソートした作戦を元にスイングデータを作成する // (優先度の高い作戦により、後から上書きする) var sortedMissions = missions.OrderBy(m => m.Priority); foreach (BattingMission mission in sortedMissions) { swingData = mission.GetSwingData(actualBallData, GameData, swingData); } return swingData; }
/// <summary> /// 構築 /// </summary> /// <param name="kind">結果種別</param> /// <param name="pitchingBallData">投球データ</param> /// <param name="battedBoll">打球</param> public PitchingBollResult(PitchingBollResultKind kind, PitchingBallData pitchingBallData, BattedBoll battedBoll) { PitchingBollResultKind = kind; PitchingBallData = pitchingBallData; BattedBoll = battedBoll; // 打球はnullで呼び出されることがあるため、nullの場合はインスタンスのみ作成しておく(NullException回避) if (BattedBoll == null) { BattedBoll = new BattedBoll(); } }
/// <summary> /// 投手に要求する投球を取得 /// </summary> /// <returns>要求する投球(コースと球種のみ設定)</returns> public PitchingBallData GetRequestPitchBoll() { // 基本作戦の「常に厳しいコース」を追加 List<PitchingMission> missions = new List<PitchingMission>(); missions.Add(new PitchingMission(PitchingMissionKind.Random)); // 智力依存で投球の作戦を追加 AddMissions(missions); // 検証用の投球作戦が指定されている場合は、他の投球作成をクリアしてそれを設定する if (PitchingMissionsForDebug.Count > 0) { missions.Clear(); missions.AddRange(PitchingMissionsForDebug); } // 要求する投球データの初期値 // (球種はランダム、コースは厳しいコース) PitchingBallData requestBall = new PitchingBallData() { IsFastBall = RandomCreater.GetBool(), PitchingCourse = PitchingCourse.Severe }; // 優先順位の低い順にソートした作戦を元にスイングデータを作成する // (優先度の高い作戦により、後から上書きする) var sortedMissions = missions.OrderBy(m => m.Priority); foreach (PitchingMission mission in sortedMissions) { requestBall = mission.GetRequestBall(GameData, requestBall); } // ウエストの場合は球種は直球にしておく(アニメーションを直球にするため) if (requestBall.PitchingCourse == PitchingCourse.PitchOut) { requestBall.IsFastBall = true; } return requestBall; }
/// <summary> /// スイングのデータを取得する /// </summary> /// <param name="ballData"></param> /// <param name="gameData"></param> /// <param name="swingData"></param> /// <returns></returns> public BattingSwingData GetSwingData(PitchingBallData ballData, GameData gameData, BattingSwingData swingData) { // 優先度の低い作戦順に処理して上書きすることを前提とするため // IsSwingとSwingKindの両方を必ずしも設定しない switch (BattingMissionKind) { #region スイングの種別選定 case BattingMissionKind.SwingStrike: // ストライクを振る swingData.IsSwing = PitchingCourseHelper.IsStrike(ballData.PitchingCourseByBattingEye); swingData.BattingSwingKind = BattingSwingKind.Normal; break; case BattingMissionKind.RollBallByAbility: // 得意なら転がせ // パワーが一定以下で、ミートがパワーより大幅に高い場合は転がす if (gameData.CurrentBatter.Power < Constants.MinValueForEfficientSwing && gameData.CurrentBatter.Meet - gameData.CurrentBatter.Power >= Constants.DeltaForRollBollByAbility) { swingData.BattingSwingKind = BattingSwingKind.RollBoll; } // 内野前進かバントシフトの場合、転がせ有利のため // パワーが一定未満か[ミート > パワー]なら実行 if ((gameData.InFielderDrawIn || gameData.BuntShift) && (gameData.CurrentBatter.Power < Constants.MinValueForEfficientSwing || gameData.CurrentBatter.Meet > gameData.CurrentBatter.Power)) { swingData.BattingSwingKind = BattingSwingKind.RollBoll; } break; case BattingMissionKind.FullSwingByAbility: // 得意なら長打狙い // 以下の条件を満たす場合に実行 // ・パワーが一定以上 // ・パワーがミート以上 // ・8回までで二死ランナーなしか二死一塁(長打狙いだと打率が大幅に下がるため) if (gameData.CurrentBatter.Power >= Constants.PowerForFullSwingByAbility && gameData.CurrentBatter.Power >= gameData.CurrentBatter.Meet && gameData.CurrentInning <= 8 && gameData.OutCount == 2 && gameData.AllRunnerMembers.Count <= 1 && gameData.SecondRunnerMember == null && gameData.ThirdRunnerMember == null) { swingData.BattingSwingKind = BattingSwingKind.FullSwing; } break; case BattingMissionKind.RightBatting: // 右打ち // 右打者であり、無死二塁で、ミート>パワーの場合、右打ちする // (一死二塁は、右打ちしない方が良い場合もあるため、自動では右打ちしない) if (!gameData.CurrentBatter.Player.IsLeftBox && gameData.OutCount == 0 && gameData.AllRunnerMembers.Count == 1 && gameData.SecondRunnerMember != null && gameData.CurrentBatter.Meet > gameData.CurrentBatter.Power) { swingData.BattingSwingKind = BattingSwingKind.RightBatting; } break; case BattingMissionKind.FullSwingByTwoStrike: // 2ストライクまで長打狙い if (gameData.StrikeCount < 2) { swingData.BattingSwingKind = BattingSwingKind.FullSwing; } break; case BattingMissionKind.NormalSwingByUser: // 通常スイング(ユーザ操作) swingData.BattingSwingKind = BattingSwingKind.Normal; break; case BattingMissionKind.FullSwingByUser: // 長打狙い(ユーザ操作) swingData.BattingSwingKind = BattingSwingKind.FullSwing; break; case BattingMissionKind.RollBallByUser: // 転がせ(ユーザ操作) swingData.BattingSwingKind = BattingSwingKind.RollBoll; // 智力が一定以上の場合、ユーザ戦術が「転がせ」であっても // 右打者であり、無死二塁の場合、右打ちする // (一死二塁は、右打ちしない方が良い場合もあるため、自動では右打ちしない) if (gameData.CurrentBatter.Wisdom >= Constants.WisdomForRightBatting && !gameData.CurrentBatter.Player.IsLeftBox && gameData.OutCount == 0 && gameData.AllRunnerMembers.Count == 1 && gameData.SecondRunnerMember != null) { swingData.BattingSwingKind = BattingSwingKind.RightBatting; } break; #endregion #region 狙い球の選定 case BattingMissionKind.TargetSweetCourseByTwoStrike: // 甘い球狙い(2ストライクまで) if (PitchingCourseHelper.IsStrike(ballData.PitchingCourseByBattingEye) && gameData.StrikeCount < 2) { if (ballData.PitchingCourseByBattingEye == PitchingCourse.Center || ballData.PitchingCourseByBattingEye == PitchingCourse.Sweet) { // ストライクでど真ん中か甘いコースのみスイング swingData.IsSwing = true; swingData.TargetBollSwingLevel = 1; } else { // 狙い球でなければスイングしない swingData.IsSwing = false; } } break; case BattingMissionKind.TargetStrongBall: // 得意球種狙い if (PitchingCourseHelper.IsStrike(ballData.PitchingCourseByBattingEye)) { // 球種能力差が大きい場合に適用 // 能力オール8で球種能力差が4でOPSが大きく上がるため、その差以上で適用 // (得意球種を投げる確率はPitchingMission.GetRequestBallメソッド参照) double fastValue = gameData.CurrentPitcherAbirity.Fastball; double breakingValue = gameData.CurrentPitcherAbirity.Breakingball; double delta = fastValue - breakingValue; if (Math.Abs(delta) >= 4) { // 狙う球種の設定 bool targetFastBall = fastValue > breakingValue; // 相手バッテリーが「得意な球種を多用」作戦の実行ができない智力の場合 // 逆に弱い方の球種を狙う(50%で弱い球種が来るならその方が狙い目) if (gameData.CurrentBatteryWisdom < Constants.WisdomForStrongBoll) { targetFastBall = !targetFastBall; } if (targetFastBall == ballData.IsFastBall) { // ストライクで狙う球種が一致した時のみスイング swingData.IsSwing = true; swingData.TargetBollSwingLevel = 1; } else { if (gameData.StrikeCount < 2) { // 狙う球種が一致しなければスイングしない swingData.IsSwing = false; } else { // 追い込まれていればファウル狙い swingData.IsSwing = true; swingData.BattingSwingKind = BattingSwingKind.Cut; } } } } break; case BattingMissionKind.TargetSweetAndFastOrBreakingBall: // 甘い直球か甘い変化球狙い // 智力が打力より圧倒的に高い場合に適用する(カットするごとにカットのための智力は減衰) if (gameData.CurrentBatter.Wisdom - gameData.FoulCountForSwing * Constants.DownCutAbilityByThrowBall - gameData.CurrentBatter.MeetAndPower >= Constants.TargetFourBollMeetAndPowerDelta) { if (PitchingCourseHelper.IsStrike(ballData.PitchingCourseByBattingEye)) { // 相手投手の能力が高い方の球種に絞って対応する bool targeFastBalll = gameData.CurrentPitcher.Fastball > gameData.CurrentPitcher.Breakingball; // 相手バッテリーが「得意な球種を多用」作戦の実行ができない智力の場合 // 逆に弱い方の球種を狙う(50%で弱い球種が来るならその方が狙い目) if (gameData.CurrentBatteryWisdom < Constants.WisdomForStrongBoll) { targeFastBalll = !targeFastBalll; } if ((ballData.PitchingCourseByBattingEye == PitchingCourse.Center || ballData.PitchingCourseByBattingEye == PitchingCourse.Sweet) && ballData.IsFastBall == targeFastBalll) { // ど真ん中か甘いコースで球種が一致時のみスイング swingData.IsSwing = true; swingData.TargetBollSwingLevel = 2; } else { if (gameData.StrikeCount < 2) { // 条件に合わなければスイングしない swingData.IsSwing = false; } else { // 追い込まれていればファウル狙い swingData.IsSwing = true; swingData.BattingSwingKind = BattingSwingKind.Cut; } } } } break; case BattingMissionKind.TargetFastBallByUser: // 直球狙い(ユーザ操作) if (PitchingCourseHelper.IsStrike(ballData.PitchingCourseByBattingEye)) { if (ballData.IsFastBall) { // ストライクで直球のときのみスイング swingData.IsSwing = true; swingData.TargetBollSwingLevel = 1; } else { if (gameData.StrikeCount< 2) { // 変化球はスイングしない swingData.IsSwing = false; } else { // 変化球でも追い込まれていればファウル狙い swingData.IsSwing = true; swingData.BattingSwingKind = BattingSwingKind.Cut; } } } break; case BattingMissionKind.TargetBreakingBallByUser: // 変化球狙い(ユーザ操作) if (PitchingCourseHelper.IsStrike(ballData.PitchingCourseByBattingEye)) { if (!ballData.IsFastBall) { // ストライクで変化球のときのみスイング swingData.IsSwing = true; swingData.TargetBollSwingLevel = 1; } else { if (gameData.StrikeCount < 2) { // 直球はスイングしない swingData.IsSwing = false; } else { // 直球でも追い込まれていればファウル狙い swingData.IsSwing = true; swingData.BattingSwingKind = BattingSwingKind.Cut; } } } break; case BattingMissionKind.TargetSweetCourseByUser: // 甘い球狙い(ユーザ操作) if (PitchingCourseHelper.IsStrike(ballData.PitchingCourseByBattingEye)) { if (ballData.PitchingCourseByBattingEye == PitchingCourse.Center || ballData.PitchingCourseByBattingEye == PitchingCourse.Sweet) { // ストライクでど真ん中か甘いコースのみスイング swingData.IsSwing = true; swingData.TargetBollSwingLevel = 1; } else { if (gameData.StrikeCount < 2) { // 甘いコースでなければスイングしない swingData.IsSwing = false; } else { // 甘いコースでなくても、追い込まれていればファウル狙い swingData.IsSwing = true; swingData.BattingSwingKind = BattingSwingKind.Cut; } } } break; case BattingMissionKind.TargetSweetFastBallByUser: // 甘い直球狙い(ユーザ操作) if (PitchingCourseHelper.IsStrike(ballData.PitchingCourseByBattingEye)) { if ((ballData.PitchingCourseByBattingEye == PitchingCourse.Center || ballData.PitchingCourseByBattingEye == PitchingCourse.Sweet) && ballData.IsFastBall) { // ど真ん中か甘いコースで直球のみスイング swingData.IsSwing = true; swingData.TargetBollSwingLevel = 2; } else { if (gameData.StrikeCount < 2) { // 条件に合わなければスイングしない swingData.IsSwing = false; } else { // 追い込まれていればファウル狙い swingData.IsSwing = true; swingData.BattingSwingKind = BattingSwingKind.Cut; } } } break; case BattingMissionKind.TargetSweetBreakingBallByUser: // 甘い変化球狙い(ユーザ操作) if (PitchingCourseHelper.IsStrike(ballData.PitchingCourseByBattingEye)) { if ((ballData.PitchingCourseByBattingEye == PitchingCourse.Center || ballData.PitchingCourseByBattingEye == PitchingCourse.Sweet) && !ballData.IsFastBall) { // ど真ん中か甘いコースで変化球のみスイング swingData.IsSwing = true; swingData.TargetBollSwingLevel = 2; } else { if (gameData.StrikeCount < 2) { // 条件に合わなければスイングしない swingData.IsSwing = false; } else { // 追い込まれていればファウル狙い swingData.IsSwing = true; swingData.BattingSwingKind = BattingSwingKind.Cut; } } } break; #endregion #region クサイ所はカット case BattingMissionKind.CutScantBall: // クサイ所はカット if (gameData.StrikeCount == 2 && (ballData.PitchingCourse == PitchingCourse.Severe || ballData.PitchingCourse == PitchingCourse.Corner) && ballData.PitchingCourseByBattingEye == PitchingCourse.ScantBall) { // 2ストライクで「厳しいボール」を誤判断で「わずかにボール」と判定した場合に // 一定確率でカットする(智力依存) GameMemberAbility memberAbility = gameData.CurrentBatterAbirity; double percent = GameMemberAbility.GetHigherValueOverBaseline(memberAbility.Wisdom, Constants.MaxCutScantBollPercent); if (RandomCreater.GetPercentValue() < percent) { swingData.IsSwing = true; swingData.BattingSwingKind = BattingSwingKind.Cut; } } break; #endregion #region 送りバント case BattingMissionKind.Bunt: // 送りバント if (gameData.UnableBunt) { break; } // 二塁→三塁へのバントは一死では効果が低いため実施しない // (一死二塁と一死一二塁はバントしない) if (gameData.OutCount == 1 && gameData.SecondRunnerMember != null) { break; } // 智力が打力以上であり // 次の打者の打者能力が現在の打者より著しく劣っていない if (gameData.CurrentBatter.Wisdom >= gameData.CurrentBatter.MeetAndPower && gameData.NextBatter.BatterAbility > gameData.CurrentBatter.BatterAbility - Constants.BatterAbilityDeltaForBunt) { // 試合出場中メンバのうち、打者能力上位3名はバントしない var goodBatters = gameData.CurrentAttackTeam.CurrentPlayingMembers.OrderByDescending(m => m.BatterAbility).Take(3); if (goodBatters.SingleOrDefault(m => m == gameData.CurrentBatter) == null) { swingData.BattingSwingKind = BattingSwingKind.Bunt; if (PitchingCourseHelper.IsStrike(ballData.PitchingCourseByBattingEye)) { swingData.IsSwing = true; } // ストライクと判断しない場合にスイングをやめる処理は行わない // クサイ球をカットするためにスイングする状態になっているのをそのまま活用してストライクを見逃さない } } break; #endregion #region 出塁狙い case BattingMissionKind.TargetFourBall: // 四球狙い // 智力が打力より圧倒的に高い場合に適用する if (gameData.CurrentBatter.Wisdom - gameData.CurrentBatter.MeetAndPower >= Constants.TargetFourBollMeetAndPowerDelta) { if (PitchingCourseHelper.IsStrike(ballData.PitchingCourseByBattingEye)) { if (gameData.StrikeCount < 2) { // 2ストライクまではスイングしない swingData.IsSwing = false; } else { // 追い込まれていればファウル狙い swingData.IsSwing = true; swingData.BattingSwingKind = BattingSwingKind.Cut; } } } break; case BattingMissionKind.SafetyBunt: // セーフティバント if (gameData.UnableSafetyBunt || gameData.CurrentBatter.SafetyBuntAbility <= gameData.CurrentBatter.MeetAndPower || (gameData.AllRunnerMembers.Count > 0 && gameData.AllRunnerMembers.Min(r => r.RunnerAbility) < Constants.RunAbilityForBuntRunner)) { // 以下の場合は実行しない // ・セーフティバント実施すべきでない状況(走力足りない、2ストライク、バントシフト) // ・「セーフティバント能力」が「ミート+パワー」以下 // ・ランナーがいる場合に走塁能力が一定以上ないランナーがいる // (足が遅すぎるとランナーがアウトになるため) break; } // セーフティバント能力が、三塁手の守備力を大幅に超えている場合に実行 // (前進守備しているかどうかで必要な差分は異なる) { double delta = (gameData.CurrentBatter.SafetyBuntAbility) - (gameData.GetDefenseMember(DefensePosition.Third).Defense); if ((gameData.InFielderDrawIn == false && delta >= Constants.DeltaForSafetyBunt) || (gameData.InFielderDrawIn == true && delta >= Constants.DeltaForBuntShift)) { swingData.BattingSwingKind = BattingSwingKind.SafetyBunt; if (PitchingCourseHelper.IsStrike(ballData.PitchingCourseByBattingEye)) { swingData.IsSwing = true; } } } break; #endregion #region 盗塁・スクイズ case BattingMissionKind.Steal: // 盗塁 if (RunnerTactics.TrySteal(gameData)) { swingData.IsSteal = true; if (gameData.StrikeCount < 2) { // 追い込まれていない場合 // (見逃す場合でもアニメーションで使用するため、スイングの種類はバントにする) swingData.IsSwing = false; swingData.BattingSwingKind = BattingSwingKind.Bunt; } else { // 追い込まれている場合 // スイング有無とスイング種別は変更しない // (カット狙いや長打狙いをやめて転がせにするとデメリットがあるため) } } break; case BattingMissionKind.Squeeze: // スクイズ if (gameData.UnableSqueeze) { break; } // 打者の智力がミート・パワーの平均より低い場合は実施しない(打った方が期待できるため) if (gameData.CurrentBatter.Wisdom < gameData.CurrentBatter.MeetAndPower) { break; } // 試合出場中メンバのうち、打者能力上位2名はスクイズしない var highBatters = gameData.CurrentAttackTeam.CurrentPlayingMembers.OrderByDescending(m => m.BatterAbility).Take(2); if (highBatters.Any(m => m == gameData.CurrentBatter)) { break; } int squeezeValue = 0; if (gameData.StrikeCount + gameData.BallCount == 0) { // 初球 squeezeValue = 10; } else if (gameData.StrikeCount == 1 && gameData.BallCount == 0) { // 2球目(ストライク先行) squeezeValue = 10; } else if (gameData.StrikeCount == 0 && gameData.BallCount == 1) { // 2球目(ボール先行) squeezeValue = 20; } else if (gameData.StrikeCount == 1 && gameData.BallCount == 1) { // 3球目の並行カウント squeezeValue = 20; } else if (gameData.StrikeCount == 2) { // 追い込まれるとスクイズなし squeezeValue = 0; } else if (gameData.StrikeCount == 0 && gameData.BallCount >= 2) { // ストライク一球もなければ逆に敬遠の可能性があるため控える squeezeValue = 10; } else if (gameData.StrikeCount == 1 && gameData.BallCount >= 2) { // ストライク一球あってボール先行であれば敬遠のリスクも低くスクイズのチャンス squeezeValue = 20; } // 指定した確率でスクイズを実行するか判定 if (RandomCreater.GetPercentValue() < squeezeValue) { swingData.AllRunnerStart = true; swingData.IsSwing = true; swingData.BattingSwingKind = BattingSwingKind.Squeeze; } break; #endregion #region ユーザ操作による最優先作戦 // スクイズ作戦を自動で実行している可能性があるため、全ランナースタートフラグをリセットする // 盗塁も自動で実行している可能性があるが、それはそれで効果的なのでフラグリセットしない case BattingMissionKind.SafetyBuntByUser: // セーフティバント(ユーザ操作) // (見逃す場合でもアニメーションで使用するため、スイングの種類はバントにする) swingData.AllRunnerStart = false; swingData.BattingSwingKind = BattingSwingKind.SafetyBunt; if (PitchingCourseHelper.IsStrike(ballData.PitchingCourseByBattingEye)) { swingData.IsSwing = true; } // ストライクと判断しない場合にスイングをやめる処理は行わない // クサイ球をカットするためにスイングする状態になっているのをそのまま活用してストライクを見逃さない break; case BattingMissionKind.BuntByUser: // 送りバント(ユーザ操作) // (見逃す場合でもアニメーションで使用するため、スイングの種類はバントにする) swingData.AllRunnerStart = false; swingData.BattingSwingKind = BattingSwingKind.Bunt; if (PitchingCourseHelper.IsStrike(ballData.PitchingCourseByBattingEye)) { swingData.IsSwing = true; } // ストライクと判断しない場合にスイングをやめる処理は行わない // クサイ球をカットするためにスイングする状態になっているのをそのまま活用してストライクを見逃さない break; case BattingMissionKind.SqueezeByUser: // スクイズ(ユーザ操作) { swingData.AllRunnerStart = true; swingData.IsSwing = true; swingData.BattingSwingKind = BattingSwingKind.Squeeze; } break; case BattingMissionKind.StealByUser: // 盗塁(ユーザ操作) // (見逃す場合でもアニメーションで使用するため、スイングの種類はバントにする) swingData.AllRunnerStart = false; swingData.IsSwing = false; swingData.BattingSwingKind = BattingSwingKind.Bunt; swingData.IsSteal = true; break; case BattingMissionKind.EndRunByUser: // エンドラン(ユーザ操作) swingData.AllRunnerStart = true; swingData.IsSwing = true; swingData.BattingSwingKind = BattingSwingKind.EndRun; break; case BattingMissionKind.WaitByUser: // 待て(ユーザ操作) swingData.AllRunnerStart = false; swingData.IsSwing = false; break; #endregion default: throw new Exception("Invalid BattingMissinKind"); } return swingData; }
/// <summary> /// リード距離を算出する /// </summary> /// <param name="baseNumber">元の塁の番号(1~3)</param> /// <param name="isStealRunner">盗塁するランナーか</param> /// <param name="gameData">試合のデータ</param> /// <param name="pitchingBallData">投球データ</param> /// <returns>リード距離</returns> public static double GetLeadDistance(int baseNumber, bool isStealRunner, GameData gameData, PitchingBallData pitchingBallData) { // メンバ能力の取得 GameMemberAbility memberAbility = GetRunnerAbility(baseNumber, gameData); // 通常のリード距離を算出 // (最小リード距離+智力依存のリード距離) double leadDistance = Constants.MinRunnerLeadDistance; { // 智力Sでそのままリード距離を算出すると、走力が低くても簡単に盗塁できてしまうため // 最大リード距離の1.5倍までをリード距離の制限とする double normalDistance = Math.Min( GameMemberAbility.GetHigherValueOverBaseline(memberAbility.Wisdom, Constants.MaxRunnerLeadDistance), Constants.MaxRunnerLeadDistance * 1.5); leadDistance += normalDistance; } // 2アウトの場合はフライアウトで戻る必要がない分、余分にリードする(智力依存) if (gameData.OutCount == 2 && !isStealRunner) { leadDistance += GameMemberAbility.GetHigherValueOverBaseline(memberAbility.Wisdom, Constants.TwoOutRunnerAdditionalLeadDistance); } // 二塁ランナーはベースカバーが離れているため余分にリードできる(智力依存) if (baseNumber == 2) { // 智力Sでも最大距離以上はリードできない制限を付ける(三盗が簡単にできてしまうようになるため) double secondDistance = GameMemberAbility.GetHigherValueOverBaseline(memberAbility.Wisdom, Constants.SecondRunnerAdditionalLeadDistance); leadDistance += Math.Min(secondDistance, Constants.SecondRunnerAdditionalLeadDistance); } // バントの場合は、ゴロを期待して余分にリードできる(固定) if (gameData.CurrentBattingBunt && !isStealRunner) { leadDistance += Constants.BuntAdditionalLeadDistance; } // 盗塁の場合は余分にリードする(智力依存) if (isStealRunner) { // 盗塁時に余分にリードする距離を加算 // (この値は小さいため、智力Sでの制限は付けない) leadDistance += GameMemberAbility.GetHigherValueOverBaseline(memberAbility.Wisdom, Constants.StealRunnerAdditionalMaxLeadDistance); // ウエストでなければリード距離を加算 // (gameData.LatestPitchingBollResult.PitchingBallDataは予想段階では確定していないため引数の値を利用) if (pitchingBallData.PitchingCourse != PitchingCourse.PitchOut) { // 球種ごとのリード距離加算 if (pitchingBallData.IsFastBall) { // 直球はそれなりにリード距離追加あり leadDistance += Constants.StealRunnerAdditionalLeadSecondByFastBall * memberAbility.RunSpeed; } else { // 変化球は多めにリード距離追加あり leadDistance += Constants.StealRunnerAdditionalLeadSecondByBreakingBall * memberAbility.RunSpeed; } // 打者に対する球数によってリードする距離を加算 // (このメソッドを呼ばれた時点で球数は+1されていることを前提に計算する) // (1球目はリード距離追加なし) if (gameData.ThrowBallCountForSteal >= 2) { // 2球目から加算 leadDistance += Constants.StealRunnerAdditionalLeadSecondByFirstThrow * memberAbility.RunSpeed; // 3球目以降は球数に応じて少しずつ加算 if (gameData.ThrowBallCountForSteal >= 3) { // 4球目以降は同じとする(加算が多くなりすぎるため) int count = Math.Min(gameData.ThrowBallCountForSteal - 1, 4); leadDistance += count * Constants.StealRunnerAdditionalLeadSecondByThrowCount * memberAbility.RunSpeed; } } } else { // ウエストの場合、むしろリード距離を減算する leadDistance -= Constants.StealRunnerMinusLeadSecondByPitchOut * memberAbility.RunSpeed; } } // 智力と走力が高すぎてリード距離がベース間距離を超えてしまう場合はベース間距離に補正する if (leadDistance > Constants.BaseDistance) { leadDistance = Constants.BaseDistance; } return leadDistance; }
/// <summary> /// 盗塁で次の塁までにかかる時間を取得する /// </summary> /// <param name="baseNumber">盗塁するランナーのベース番号</param> /// <param name="gameData">試合のデータ</param> /// <param name="pitchingBallData">想定する投球データ</param> /// <returns>盗塁で次の塁までにかかる時間</returns> private static int GetStealTime(int baseNumber, GameData gameData, PitchingBallData pitchingBallData) { // リード距離の取得 double leadDistance = GetLeadDistance(baseNumber, true, gameData, pitchingBallData); // 次の塁までの残り距離の取得 double remainingDistance = Constants.BaseDistance - leadDistance; // 次の塁に到達するまでの時間を取得 GameMemberAbility memberAbility = GetRunnerAbility(baseNumber, gameData); int remainingTime = (int)Math.Ceiling(remainingDistance / (memberAbility.RunSpeed)); // 時間を返す return remainingTime; }
/// <summary> /// 盗塁で次の塁までにかかる時間を取得する(ウエスト) /// </summary> /// <param name="baseNumber">盗塁するランナーのベース番号</param> /// <param name="gameData">試合のデータ</param> /// <returns>盗塁で次の塁までにかかる時間</returns> public static int GetStealTimeByPitchOut(int baseNumber, GameData gameData) { // 想定する投球データを作成 PitchingBallData virtualPitchingBallData = new PitchingBallData() { IsFastBall = true, PitchingCourse = PitchingCourse.PitchOut }; // 時間を取得 return GetStealTime(baseNumber, gameData, virtualPitchingBallData); }
/// <summary> /// 盗塁で次の塁までにかかる時間を取得する(変化球) /// </summary> /// <param name="baseNumber">盗塁するランナーのベース番号</param> /// <param name="gameData">試合のデータ</param> /// <returns>盗塁で次の塁までにかかる時間</returns> public static int GetStealTimeByBreakingBall(int baseNumber, GameData gameData) { // 想定する投球データを作成 PitchingBallData virtualPitchingBallData = new PitchingBallData() { IsFastBall = false, PitchingCourse = PitchingCourse.Severe }; // 時間を取得 return GetStealTime(baseNumber, gameData, virtualPitchingBallData); }
/// <summary> /// 投手に要求する投球データを取得する /// </summary> /// <param name="gameData"></param> /// <param name="requestBall"></param> /// <returns></returns> public PitchingBallData GetRequestBall(GameData gameData, PitchingBallData requestBall) { // 作戦ごとに球種とコースを設定 switch(PitchingMissionKind) { case PitchingMissionKind.Random: // ランダム // 甘いコース、厳しいコース、わずかにボールをランダムに投球 // (ストライク先行とくらべて球数があまり変わらないようにボール球を少し多めにする) int randomValue = RandomCreater.GetPercentValue(); if (randomValue < 30) { requestBall.PitchingCourse = PitchingCourse.Sweet; } else if (randomValue < 60) { requestBall.PitchingCourse = PitchingCourse.Severe; } else { requestBall.PitchingCourse = PitchingCourse.ScantBall; } break; case PitchingMissionKind.StrikeFirst: // ストライク先行 #region ストライク先行 { // ストライクが先行している場合のみボールを投げる int strikeValue; if (gameData.StrikeCount + gameData.BallCount == 0) { // 初球はストライク strikeValue = 75; } else if (gameData.StrikeCount == 1 && gameData.BallCount == 0) { // 2球目:ストライク先行ならボール strikeValue = 25; } else if (gameData.StrikeCount == 0 && gameData.BallCount == 1) { // 2球目:ボール先行ならストライクをかなり優先 strikeValue = 90; } else if (gameData.StrikeCount == 1 && gameData.BallCount == 1) { // 3球目:並行カウントならストライク strikeValue = 75; } else if (gameData.StrikeCount == 2 && gameData.BallCount == 0) { // 3球目:ストライクが2つ先行していればストライクは不要 // (失投でストライクになるため「待て」すればいいというわけではない) strikeValue = 0; } else if (gameData.BallCount >= 2) { // 3球目以降:2ボール以上であればストライクを投げる strikeValue = 100; } else if (gameData.StrikeCount == 2 && gameData.BallCount == 1) { // 4球目:ストライクが1つ先行していればボールをかなり優先 strikeValue = 10; } else { throw new Exception("Invalid Count"); } // 指定した確率でストライクを投球するか判定 if (RandomCreater.GetPercentValue() < strikeValue) { requestBall.PitchingCourse = PitchingCourse.Severe; } else { requestBall.PitchingCourse = PitchingCourse.ScantBall; } } #endregion break; case PitchingMissionKind.UtilizeBall: case PitchingMissionKind.UtilizeBallByUser: // ボールを有効活用 if (PitchingMissionKind == PitchingMissionKind.UtilizeBall && gameData.IsScoringPosition == false) { // 体力温存のため得点圏の場合のみ適用 // (デバッグ用の場合は常に実行する) break; } #region ボールを有効活用 // 並行カウントか2ボールでなければボールを投球 { int strikeValue; if (gameData.StrikeCount + gameData.BallCount == 0) { // 初球はボール strikeValue = 25; } else if (gameData.StrikeCount == 1 && gameData.BallCount == 0) { // 2球目:ストライク先行ならボールをかなり優先 strikeValue = 10; } else if (gameData.StrikeCount == 0 && gameData.BallCount == 1) { // 2球目:ボール先行ならストライク strikeValue = 75; } else if (gameData.StrikeCount == 1 && gameData.BallCount == 1) { // 3球目:並行カウントならボール strikeValue = 25; } else if (gameData.StrikeCount == 2 && gameData.BallCount == 0) { // 3球目:ストライクが2つ先行していればストライクは不要 // (失投でストライクになるため「待て」すればいいというわけではない) strikeValue = 0; } else if (gameData.StrikeCount < 2 && gameData.BallCount >= 2) { // 3球目以降:2ストライクでなく、2ボール以上であればストライクを投げる strikeValue = 100; } else if (gameData.StrikeCount == 2 && gameData.BallCount == 1) { // 4球目:ストライクが1つ先行していればストライク不要 strikeValue = 0; } else if (gameData.StrikeCount == 2 && gameData.BallCount == 2) { // 5球目:2ストライクで2ボールであればボール球を振らせる確率にかけて多少ボールも投げる strikeValue = 100; } else if (gameData.BallCount == 3) { // 3ボールは無条件でストライク strikeValue = 100; } else { throw new Exception("Invalid Count"); } // 指定した確率でストライクを投球するか判定 if (RandomCreater.GetPercentValue() < strikeValue) { requestBall.PitchingCourse = PitchingCourse.Severe; } else { requestBall.PitchingCourse = PitchingCourse.ScantBall; } } #endregion break; case PitchingMissionKind.UtilizePitchOut: // ウエストを有効活用 #region ウエストを活用 // ランナーがいる場合のみ適用 if (gameData.AllRunnerMembers.Count > 0) { // 盗塁対策 // 事実としての判断タイプと、ランナーの判断タイプをもとに判定する RunnerTactics.StealJudgeType realJudgeType = RunnerTactics.GetRealStealJudgeType(gameData); RunnerTactics.StealJudgeType runnerJudgeType = RunnerTactics.GetStealJudgeType(gameData); int pitchOutValue = 0; switch (runnerJudgeType) { case RunnerTactics.StealJudgeType.Invalid: case RunnerTactics.StealJudgeType.Impossible: // 盗塁不可であれば何もしない break; case RunnerTactics.StealJudgeType.SuccessIfBreakingBoll: // 変化球で盗塁可能であれば小確率でウエストする // 実際が常に成功であればウエストしない // (ランナー側も盗塁しなける確率は低いため強制ストライクにしない) // また、実際の判定も「変化球なら成功」ならウエストの必要なし if (realJudgeType == RunnerTactics.StealJudgeType.Success || realJudgeType == RunnerTactics.StealJudgeType.SuccessIfBreakingBoll) { break; } // 以下、実際の判定が「直球なら成功」の場合 // 2アウトでランナー一塁のみ以外であれば、そもそも盗塁してこないので // その条件以外はウエストしない if (gameData.OutCount < 2 || gameData.FirstRunnerMember == null || gameData.SecondRunnerMember != null || gameData.ThirdRunnerMember != null) { break; } if (gameData.StrikeCount + gameData.BallCount == 0) { // 初球はそれなりに警戒 pitchOutValue = 20; } else if (gameData.StrikeCount == 1 && gameData.BallCount == 0) { // 2球目もそれなりに警戒 pitchOutValue = 20; } else if (gameData.StrikeCount == 0 && gameData.BallCount == 1) { // 2球目もそれなりに警戒(ボール先行は不利になるため確率少ない) pitchOutValue = 10; } // 3球目以降は盗塁してこないと判断してウエストしない // 指定した確率でウエストを実行するか判定 if (RandomCreater.GetPercentValue() < pitchOutValue) { requestBall.PitchingCourse = PitchingCourse.PitchOut; } break; case RunnerTactics.StealJudgeType.SuccessIfFastBoll: // 直球で盗塁可能であれば高確率でウエストする if (realJudgeType == RunnerTactics.StealJudgeType.Success) { // 実際が常に成功であれば盗塁される間にカウント稼ぐためにストライクを投げる requestBall.PitchingCourse = PitchingCourse.Severe; break; } // 以下、実際の判定も「直球なら成功」の場合 if (gameData.StrikeCount + gameData.BallCount == 0) { // 初球はかなり警戒 pitchOutValue = 33; } else if (gameData.StrikeCount == 1 && gameData.BallCount == 0) { // 2球目(ストライク先行)もかなり警戒 pitchOutValue = 25; } else if (gameData.StrikeCount == 0 && gameData.BallCount == 1) { // 2球目(ボール先行)もかなり警戒 pitchOutValue = 20; } else if (gameData.StrikeCount == 1 && gameData.BallCount == 1) { // 3球目の並行カウントはそれなりに警戒 pitchOutValue = 15; } else if (gameData.StrikeCount == 2 && requestBall.IsStrike == false) { // 追い込んでいればほとんど盗塁しないと判定 // ただしどうせボール球を投げる予定であれば一定確率でウエストする pitchOutValue = 10; } else if (gameData.BallCount >= 2) { // ボール先行し過ぎると不利なため2ボール以降はウエストしない pitchOutValue = 0; } // 三盗は慎重に行われるため、半分の確率とする if (gameData.StealTargetRunnerNumber == 2) { pitchOutValue /= 2; } // 指定した確率でウエストを実行するか判定 if (RandomCreater.GetPercentValue() < pitchOutValue) { requestBall.PitchingCourse = PitchingCourse.PitchOut; } else if (gameData.StrikeCount <= 1 && gameData.BallCount <= 1) { // 1~3球目でウエストしない場合、カウントを悪くしないために // もともとボール球を指定していてもストライクに変更する requestBall.PitchingCourse = PitchingCourse.Severe; } break; case RunnerTactics.StealJudgeType.Success: // 常に成功であれば100%盗塁されるため // 確実にカウント稼ぐために甘いコースに投げる requestBall.PitchingCourse = PitchingCourse.Sweet; break; } // スクイズ対策 // (盗塁とスクイズの両方警戒の場合、スクイズの警戒で上書きする) if (gameData.UnableSqueeze) { break; } if (gameData.StrikeCount + gameData.BallCount == 0) { // 初球はかなり警戒 pitchOutValue = 33; } else if (gameData.StrikeCount == 1 && gameData.BallCount == 0) { // 2球目(ストライク先行)もかなり警戒 pitchOutValue = 33; } else if (gameData.StrikeCount == 0 && gameData.BallCount == 1) { // 2球目(ボール先行)もかなり警戒 pitchOutValue = 25; } else if (gameData.StrikeCount == 1 && gameData.BallCount == 1) { // 3球目の並行カウントはそれなりに警戒 pitchOutValue = 20; } else if (gameData.StrikeCount == 2) { // 追い込んでいればスクイズしないと判断 pitchOutValue = 0; } else if (gameData.BallCount >= 2) { // ボール先行し過ぎると不利なため2ボール以降はウエストしない pitchOutValue = 0; } // 指定した確率でウエストを実行するか判定 if (RandomCreater.GetPercentValue() < pitchOutValue) { requestBall.PitchingCourse = PitchingCourse.PitchOut; } else if (gameData.StrikeCount <= 1 && gameData.BallCount <= 1) { // 1~3球目でウエストしない場合、カウントを悪くしないために // もともとボール球を指定していてもストライクに変更する requestBall.PitchingCourse = PitchingCourse.Severe; } } break; #endregion case PitchingMissionKind.SavingPower: // 格下には省エネ // 以下の条件をすべて満たす場合に適用 // ・序盤で同点以上か、中盤以降で2点差以上で勝っている // ・1アウトか2アウト // ・ランナー無し // ・打者能力が格下(すべてストライク狙いで選球眼の寄与率が低いため打者能力はミート&パワーとする) if (((gameData.CurrentInning <= 3 && gameData.RunScoreDeltaForAttackTeam <= 0) || gameData.RunScoreDeltaForAttackTeam <= -2) && gameData.OutCount > 0 && gameData.AllRunnerMembers.Count == 0 && gameData.CurrentPitcherAbirity.PitcherTotalConsideringStamina - gameData.CurrentBatterAbirity.MeetAndPower > Constants.DeltaAbilityForSavingPower) { // 厳しいコースのみを投げる requestBall.PitchingCourse = PitchingCourse.Severe; } break; case PitchingMissionKind.AvoidFourBall: // 3ボールなら甘いコース // 3ボールでランナー無しか一塁ランナーがいる場合は甘いコース // (一塁が空いていればランナー出しても構わないため甘いコースにしない) if (gameData.BallCount == 3 && (gameData.AllRunnerMembers.Count == 0 || gameData.FirstRunnerMember != null)) { requestBall.PitchingCourse = PitchingCourse.Sweet; } break; case PitchingMissionKind.StrongBall: // 得意な球種を多用 { // 能力差1につき4%を補正(13以上の差がある場合は1球種のみとなる) double fastValue = gameData.CurrentPitcherAbirity.Fastball; double breakingValue = gameData.CurrentPitcherAbirity.Breakingball; double delta = fastValue - breakingValue; double percent = 50 + delta * 4; requestBall.IsFastBall = RandomCreater.GetPercentValue() < percent; } break; case PitchingMissionKind.StrongBallByChangeBollKind: // 球種変更補正を含めた強い球種を多用 { // 球種変更補正を含めたときに、能力差1につき6%を補正 //(1差で6%、9差以上で100%確定) double originFastBall = gameData.CurrentPitcherAbirity.Fastball; double originBreakingBall = gameData.CurrentPitcherAbirity.Breakingball; double fastValue = originFastBall + GameManager.GetOffsetValueByChabgeBollKind(gameData, true); double breakingValue = originBreakingBall + GameManager.GetOffsetValueByChabgeBollKind(gameData, false); double delta = fastValue - breakingValue; double percent = 50 + delta * 6; requestBall.IsFastBall = RandomCreater.GetPercentValue() < percent; // 同じ球種を続けて投げている場合、元々弱い球種が強い球種を超えた場合は // 補正を受けるために必ず球種を切り替える // (ただしストライクの場合。ボールの時に補正の効いた球を投げるのはもったいないため) if (gameData.ContinueingSameBollCount >= 3 && PitchingCourseHelper.IsStrike(requestBall.PitchingCourse)) { if ((originFastBall < originBreakingBall && fastValue > breakingValue)) { requestBall.IsFastBall = true; } else if (originFastBall > originBreakingBall && fastValue < breakingValue) { requestBall.IsFastBall = false; } } } break; case PitchingMissionKind.AllSevere: // 常に厳しいコース requestBall.PitchingCourse = PitchingCourse.Severe; break; default: throw new Exception("Invalid PitchingMissionKind"); } // デバッグ用(直球確率がデバッグ用に変更されている場合はその確率に従って投げる) if (Constants.FastBallPercent != 50) { requestBall.IsFastBall = RandomCreater.GetPercentValue() < Constants.FastBallPercent; } return requestBall; }
/// <summary> /// 投球を捕逸するか(捕逸する場合は打球データに設定) /// </summary> /// <param name="actualBall"></param> /// <param name="result"></param> private void JudgePassBoll(PitchingBallData actualBall, PitchingBollResult result) { // デバッグでの確率検証中は捕逸しない if (Constants.IsDebugValidationMode) { return; } // 三振か四死球かランナーがいない場合は捕逸しない if (GameData.StrikeCount == 3 || GameData.BallCount == 4 || result.PitchingBollResultKind == PitchingBollResultKind.DeadBoll || GameData.AllRunnerMembers.Count == 0) { return; } // ウエスト、ど真ん中、甘いコースは捕逸しない if (actualBall.PitchingCourse == PitchingCourse.PitchOut || actualBall.PitchingCourse == PitchingCourse.Center || actualBall.PitchingCourse == PitchingCourse.Sweet) { return; } // 捕球成功率 // =(固定90%)+(守備力補正10%)-(守備力とコースによる変動25%) // =(固定9/10)+ (守備力1/9)- (守備力とコースによる変動25%) // 厳しいコース、わずかにボール:期待値=90%+(2.5~10%) // 完全にボール:期待値=90%+(2.5~10%)-(0~9.4%) // 暴投 :期待値=90%+(2.5~10%)-(0~18.8%) GameMemberAbility memberAbility = GameData.GetDefenseMemberAbility(DefensePosition.Catcher); double JudgmentAbilityVallue = Constants.MaxAbilityValue * 9 / 10 + memberAbility.Defense / 10; if (actualBall.PitchingCourse == PitchingCourse.NormalBoll) { // 完全にボールの場合、最小値5で最大値20ならば、実質0%~9.375%の成功率低下 JudgmentAbilityVallue -= GameMemberAbility.GetLowerValue(memberAbility.Defense, Constants.MaxAbilityValue) / 8; } if (actualBall.PitchingCourse == PitchingCourse.WildPitchBoll) { // 暴投の場合、最小値5で最大値20ならば、実質0%~18.75%の成功率低下 JudgmentAbilityVallue -= GameMemberAbility.GetLowerValue(memberAbility.Defense, Constants.MaxAbilityValue) / 4; } int failDelta; if (!memberAbility.JudgByRandom(JudgmentAbilityVallue, out failDelta)) { // 捕逸する // 打球と同じ扱いにするため、打球データを設定する // 打球速度は走塁速度と同程度の速度をランダムに設定(捕手が追いつける程度にするため) result.BattedBoll = new BattedBoll(); result.BattedBoll.IsPassBoll = true; result.BattedBoll.SideDirection = RandomCreater.GetRandomValue(-190, -170); int bollSpeedValue = RandomCreater.GetRandomValue((int)Constants.SpecialAbilityBasicValue, (int)Constants.MaxAbilityValue); result.BattedBoll.Speed = bollSpeedValue * Constants.RunSpeedOffSet; // 成績更新(わかりやすいようにすべて投手の暴投としてカウントする) GameData.AddWildPitch(GameData.CurrentPitcherAbirity.GameMember); } }
/// <summary> /// 一球の投球に対するスイング結果を取得 /// </summary> /// <param name="actualBall">投球データ</param> /// <returns>一球の投球の結果</returns> private PitchingBollResult GetPitchingBollResult(PitchingBallData actualBall) { // 打者の選球結果 PitchingCourse judgmentCourse = GetJudgmentCourse(actualBall); actualBall.PitchingCourseByBattingEye = judgmentCourse; // 打撃のスイングデータを取得する // (打者能力の参照値とするため、試合データに反映) { BattingMissionManager battingMissionManager = new BattingMissionManager(GameData); GameData.CurrentBattingSwingData = battingMissionManager.GetSwingData(actualBall); } // スイングしなければカウントを進める if (!GameData.CurrentBattingSwingData.IsSwing) { // ストライクか bool isStrike = PitchingCourseHelper.IsStrike(actualBall.PitchingCourse); if (isStrike) { // 見逃しストライク return new PitchingBollResult(PitchingBollResultKind.NoSwingStrike, actualBall, null); } else { if (actualBall.PitchingCourse == PitchingCourse.WildPitchBoll && RandomCreater.GetPercentValue() < Constants.DeadBollPercent) { // 死球 return new PitchingBollResult(PitchingBollResultKind.DeadBoll, actualBall, null); } else { // ボール return new PitchingBollResult(PitchingBollResultKind.Boll, actualBall, null); } } } // バットに当たるか bool metBoll; { // バットに当たる率 // =(固定65%)±(変動25%)-(直球なら+10%) // =(固定6.5/10)+{(ミート)-(球速)-(変化球)} * 2.5 / 10 // ※エンドランは特別補正有り // ※制球の空振りへの寄与はない(打者にとって見逃しの意味をもたせるため) GameMemberAbility memberAbility = GameData.CurrentBatterAbirity; double abilityValue = Constants.MaxAbilityValue * 6.5 / 10 + (memberAbility.Meet - actualBall.BollSpeed * 10 / 10 - actualBall.BreakingAmount) * 2.5 / 10; // 直球はバットに当たりやすい(プロ野球での直球の空振り率は良くても10%) // ただし、カットは直球も変化球も同様の確率とする(そうしないと変化球狙いが有効過ぎるため) if (actualBall.IsFastBall && GameData.CurrentBattingSwingData.BattingSwingKind != BattingSwingKind.Cut) { abilityValue += Constants.MaxAbilityValue * 1 / 10; } // エンドランの場合は10%追加 if (GameData.CurrentBattingSwingData.BattingSwingKind == BattingSwingKind.EndRun) { abilityValue += Constants.MaxAbilityValue * 1 / 10; } double percent = GameMemberAbility.GetHigherValue(abilityValue, 100); // ウエストボールにスクイズやエンドランを当てにくいようにする if (actualBall.PitchingCourse == PitchingCourse.PitchOut) { percent -= 40; } // バットに当たるか確率で判定 metBoll = JudgByRandom(percent); } // バットに当たらなければカウントを進める if (!metBoll) { return new PitchingBollResult(PitchingBollResultKind.SwingMiss, actualBall, null); } // バットに当たった場合、打球データを取得 BattedBoll battedBoll = GetBattedBoll(actualBall); // バッティング結果を取得 return new PitchingBollResult(PitchingBollResultKind.BattingBoll, actualBall, battedBoll); }
/// <summary> /// 打者の選球結果を返す /// </summary> /// <param name="actualBall">投球データ</param> /// <returns>選球結果</returns> private PitchingCourse GetJudgmentCourse(PitchingBallData actualBall) { PitchingCourse judgmentCourse; // 成功率=(固定70%)±(変動40%) // =(固定7/10)+{(選球眼)-(球速)-(変化球)}* 4 / 10 // 2ストライク前:ストライク 70%、ボール 50% // 2ストライク時:ストライク 80%、ボール 40% GameMemberAbility memberAbility = GameData.CurrentBatterAbirity; double abilityValue = (Constants.MaxAbilityValue * 7 / 10) + (memberAbility.BattingEye - actualBall.BollSpeed - actualBall.BreakingAmount) * 4 / 10; // ストライクかボールかで補正 // 2ストライクの場合、見逃し三振を防ぐためにストライクの精度は上がり、ボール球の精度が下がる PitchingCourse targetCourse = actualBall.PitchingCourse; if (PitchingCourseHelper.IsStrike(targetCourse) == true) { if (GameData.StrikeCount == 2) abilityValue += (Constants.MaxAbilityValue * 1 / 10); } else { abilityValue -= (Constants.MaxAbilityValue * 2 / 10); if (GameData.StrikeCount == 2) abilityValue -= (Constants.MaxAbilityValue * 1 / 10); } // 選球判断 int failDelta; if (targetCourse == PitchingCourse.PitchOut || memberAbility.JudgByRandom(abilityValue, out failDelta)) { // 正しく選球完了(ウエストの場合は間違えない) judgmentCourse = targetCourse; } else { // 選球失敗(選球は1段階しかずれない) // ストライクはボール方向に誤判断、ボールはストライク方向に誤判断する bool isInOrOut = !PitchingCourseHelper.IsStrike(targetCourse); judgmentCourse = MistakeControl(targetCourse, 1, isInOrOut); } return judgmentCourse; }
/// <summary> /// 打球データを取得 /// </summary> /// <param name="actualBall"></param> /// <returns></returns> private BattedBoll GetBattedBoll(PitchingBallData actualBall) { #region 事前準備 BattedBoll battedBoll = new BattedBoll(); GameMemberAbility memberAbility = GameData.CurrentBatterAbirity; // パワーは一発長打を表すためにランダムに変動させる double powerWithOffset = GetVallueByRandomOffset(memberAbility.Power); #endregion #region 芯を捉えるか // 芯を捉える率 // =(固定60%)±(変動30%) // =(固定6/10)+{(ミート8/10)+(パワー2/10)-(球速2/10)-(変化球7/10)-(制球3/10)} * 3 / 10 // 直球の中央値 (減算0.6):60% + 15% = 75% // 変化球の中央値(減算1.0):60% + 0% = 60% bool isHitCore; { double abilityValue = Constants.MaxAbilityValue * 6 / 10 + (memberAbility.Meet * 8 / 10 + memberAbility.Power * 2 / 10 - actualBall.BollSpeed * 2 / 10 - actualBall.BreakingAmount * 7 / 10 - actualBall.CourseValue * 3 / 10) * 3 / 10; double percent = GameMemberAbility.GetHigherValue(abilityValue, 100); // 芯を捉えるか確率で判定 isHitCore = JudgByRandom(percent); } #endregion #region インパクト率 // インパクト率 // =(固定50%)±(変動30%) // =(固定5/10)±{(ミート5/10)+(パワー5/10)-(球速10/10)-(制球0/10)} * 3 / 10 // ※制球はファウル率で十分効果が出るため、空振り率とインパクト率への寄与は小さい // ※パワーは0%~400%の幅で分散した値とする(パワーだけでも稀に長打が出るようにするため) // 直球の中央値 (減算1.0):固定 - 0% = 50% // 変化球の中央値(減算0.7):固定 + 30% = 80% { double abilityRasio = memberAbility.Meet * 5 / 10 + powerWithOffset * 5 / 10 - actualBall.BollSpeed * 10 / 10 - actualBall.CourseValue * 0 / 10; double abilityValue = Constants.MaxAbilityValue * 5 / 10 + abilityRasio * 3 / 10; double impactPercent = GameMemberAbility.GetHigherValue(abilityValue, 100); // インパクト率をランダム値で補正しない(パワー値でランダム性があるため) //impactPercent += RandomCreater.GetRandomValue(-5, 5); // 芯をとらえていなければインパクト率は半減 if (!isHitCore) { impactPercent *= 0.5; } // インパクト率の設定 impactPercent = Math.Max(impactPercent, 0); impactPercent = Math.Min(impactPercent, 100); battedBoll.ImpactPercent = impactPercent; } #endregion #region 打球速度 // 打球速度(通常時とバント時で別処理) if (!GameData.CurrentBattingBunt) { // 通常の打球速度 // ={(固定8/10)+(変動2/10)}×{(固定8/10)+(インパクト率2/10)} // ※変動2/10={(ミート0/10)(パワー10/10)}* 2 / 10 double battedSpeedRasioValue = Constants.MaxAbilityValue * 8 / 10 + (memberAbility.Meet * 0 / 10 + powerWithOffset * 10 / 10) * 2 / 10; battedBoll.Speed = (battedSpeedRasioValue * (80 + battedBoll.ImpactPercent * 2/ 10) / 100) * Constants.BattedBollSpeedOffSet; } else { // バントかスクイズの打球速度 // =(最善速度)×(変動率0.2倍~1.6倍) // ※強すぎると遊ゴロになるため、強すぎ補正はあまりひどくしない代わりに、強すぎの確率を低くする double errorPercent; if (RandomCreater.GetPercentValue() < 33) { // 強過ぎ補正(任意の比率をプラスする) // 能力による変動が30%、能力に限らない変動が30%で、最善速度に最大60%分が加算される errorPercent = GameMemberAbility.GetLowerValueOverBaseline(memberAbility.Wisdom, Constants.MinPlusErrorPercentByBunt); errorPercent += Constants.MinPlusErrorPercentByBunt; errorPercent = RandomCreater.GetRandomValue(0, (int)errorPercent); } else { // 弱過ぎ補正(任意の比率をマイナスする) // 能力による変動が40%、能力に限らない変動が40%で、最善速度に最大80%分が減算される errorPercent = -GameMemberAbility.GetLowerValueOverBaseline(memberAbility.Wisdom, Constants.MinMinusErrorPercentByBunt); errorPercent -= Constants.MinMinusErrorPercentByBunt; errorPercent = RandomCreater.GetRandomValue((int)errorPercent, 0); } battedBoll.Speed = Constants.BestBuntBollSpeed * (100 + errorPercent) / 100; } #endregion #region ファウル方向か // ファウル方向への打球になるか bool isFoulDirection = false; { // ファウル率 double foulPercent = 0; // 通常時とバントで別処理 if (!GameData.CurrentBattingBunt) { if (isHitCore) { // 芯を捉えた場合、コースが厳しいほどファウルになりやすい // ストライクのファウル率 // =(固定15%)±(変動40%) // =(固定15%)±(40%)×{(コースの厳しさ)-(ミート)}/(能力値のMAX値) double abilityValue = actualBall.CourseValue - memberAbility.Meet; foulPercent = 15 + Constants.FoulPercentByControll * (abilityValue / Constants.MaxAbilityValue); if (!PitchingCourseHelper.IsStrike(actualBall.PitchingCourse) && actualBall.MistakeLevel == 0) { // 失投でないボール球は芯を捉えた場合にファウルになりやすい(凡打はファウルになりにくい) // (失投の場合は効果なし) foulPercent += Constants.FoutPercentByBoll; } } else { // 芯を捉えなかった場合、ミートが高いほどファウルになりやすい // 芯を外した際のファウル率 // =(固定25%)±(変動0%) // =(固定25%)±(0%)×{(ミート)- (コースの厳しさ)}/(能力値のMAX値) // (制球の効果を抑制するため変動なし) double abilityValue = memberAbility.Meet - actualBall.CourseValue; foulPercent = 25 + 0 * (abilityValue / Constants.MaxAbilityValue); } } else { // バント時のファウル率 // =(固定15%)±(変動30%)+(ボール球のファウル率) // =(固定15%)±(30%)×{(智力)-(球速)-(変化球)}+(ボール球のファウル率) double abilityValue = memberAbility.Wisdom - actualBall.BollSpeed - actualBall.BreakingAmount; foulPercent = 15 + Constants.FoulPercentByBunt * (abilityValue / Constants.MaxAbilityValue); if (!PitchingCourseHelper.IsStrike(actualBall.PitchingCourse) && actualBall.MistakeLevel == 0) { // 失投でないボール球はファウル確率アップ foulPercent += Constants.FoutPercentByBoll; } } // マイナスにならないように補正 foulPercent = Math.Max(foulPercent, 0); if (GameData.CurrentBattingSwingData.BattingSwingKind == BattingSwingKind.Cut) { // カット打法の場合は必ずファウル // (ファウルフライで凡退する可能性もあるため、方向はせめて100%にする) foulPercent = 100; } // ファウル方向になるか確率判定 if (RandomCreater.GetPercentValue() < foulPercent) { // ファウル方向確定 isFoulDirection = true; // 通常時とバントで別処理 if (!GameData.CurrentBattingBunt) { // 通常時のファウル方向設定 if (!isHitCore) { // インパクト率が低い場合は後方へファウル if (RandomCreater.GetBool()) { battedBoll.SideDirection = RandomCreater.GetRandomValue(140, 180); } else { battedBoll.SideDirection = RandomCreater.GetRandomValue(-180, -140); } } else { // インパクト率が高ければ一塁線か三塁線にファウル if (RandomCreater.GetBool()) { battedBoll.SideDirection = RandomCreater.GetRandomValue(46, 60); } else { battedBoll.SideDirection = RandomCreater.GetRandomValue(-60, -46); } } } else { // バント時のファウル方向設定 bool firstBaseDirection = DecideDirectionForBunt(); if (firstBaseDirection) { battedBoll.SideDirection = RandomCreater.GetRandomValue(46, 60); } else { battedBoll.SideDirection = RandomCreater.GetRandomValue(-60, -46); } } } } #endregion #region フライかゴロか // フライかゴロか設定する bool isFly = RandomCreater.GetPercentValue() < Constants.FlyPercent; // 作戦指示があればフライかゴロを上書きする { // 作戦通りにフライまたはゴロになる成功率 // =(固定60%)+(変動40%) // =(固定6/10)+(智力4/10) double wisdomValue = Constants.MaxAbilityValue * 6 / 10 + memberAbility.Wisdom * 4 / 10; double percent = GameMemberAbility.GetHigherValue(wisdomValue, 100); if (RandomCreater.GetPercentValue() < percent) { if (GameData.CurrentBattingSwingData.IsFly != null) { isFly = (bool)GameData.CurrentBattingSwingData.IsFly; } } } #endregion #region フライ/ゴロの特性で打球データ作成 // フライとゴロのそれぞれの打球データの作成 if (isFly) { // フライの場合 // 打球速度の低下 // (ゴロは内野の隙間を抜く必要があるから速い必要があり、ゴロと同じだと本塁打増えすぎる) battedBoll.Speed *= Constants.DownBattedBollSpeedByFly; // 落下時間を設定 // (この時点で設定する落下時間は45度の理想的な角度とする) double dropTimeBasic = battedBoll.Speed * Constants.DropTimeOffSet; double downRasio = 0; if (!isHitCore) { // 芯をとらえなかった場合はポップフライ downRasio = 50; battedBoll.Speed *= downRasio / 100; battedBoll.DropTime = (int)(dropTimeBasic * 100 / downRasio); } else { // それ以外は「ライナー性の弾道」か「高く上がり過ぎ」がランダムに発生 // (本塁打率はあまり変わらないが安打率はライナー性の弾道の方が高い) // ライナー性の弾道確率 // =(固定10%)+(40%)*(ミート&パワー) // ※守備側の能力が高いほどフライがとられやすくなるための対策 double plusPercent = GameMemberAbility.GetHigherValue(memberAbility.MeetAndPower, 40); double percent = Constants.MinLinerPercent + plusPercent; bool isLiner = JudgByRandom(percent); if (isLiner) { // ライナー性の弾道は、インパクト率が高いほど落下時間を増やす int randamValue = RandomCreater.GetRandomValue(-10, 10); battedBoll.DropTime = (int)(dropTimeBasic * (50 + battedBoll.ImpactPercent * 5 / 10 + randamValue) / 100); } else { // 高く上がり過ぎの場合、打球速度低下(ほとんど外野フライか本塁打になる) downRasio = 50 + battedBoll.ImpactPercent * 5 / 10; battedBoll.Speed *= downRasio / 100; battedBoll.DropTime = (int)(dropTimeBasic * 100 / downRasio); } } // 落下時間を一定以上に補正 battedBoll.DropTime = Math.Max(battedBoll.DropTime, Constants.MinDropTime); // フライの場合、打球方向はランダム) if (!isFoulDirection) { battedBoll.SideDirection = RandomCreater.GetRandomValue(-45, 45); // 特殊能力「プルヒッター」は超引っ張りの確率アップ if (GameData.CurrentBatter.Player.FielderAbility.SpecialFilederAbility == SpecialFilederAbility.PullHitter && RandomCreater.GetPercentValue() < 5) { battedBoll.SideDirection = RandomCreater.GetRandomValue(-45, -40); } } } else { // ゴロ battedBoll.DropTime = 0; // パワーが高くてもボテボテの凡打を時々打つように // インパクト率が小さい場合はランダムで打球速度を減速(進塁打や内安打になる可能性はある) if (!isHitCore && !GameData.CurrentBattingBunt) { battedBoll.Speed *= (double)RandomCreater.GetRandomValue(10, 70) / 100; } // ファウルでなければ打球方向を設定 if (!isFoulDirection) { if (!GameData.CurrentBattingBunt) { // 打球方向(左右)は内野の間を狙う // 守備の中間地点の角度を算出して間を狙う // ミート力が一定以上ある場合のみ、角度の誤差が小さくなる int abilityErrorValue = GameMemberAbility.GetRandamRoundValue(GameMemberAbility.GetLowerValueOverBaseline(memberAbility.MeetAndPower, Constants.MaxBattedBollSideDirectionError)); int errorValue = abilityErrorValue + Constants.MinBattedBollSideDirectionError; // 内野前進の場合はヒットがでやすいように角度誤差を小さくする if (GameData.InFielderDrawIn) { errorValue -= 1; } // 打球方向の設定 // (二塁手と遊撃手へのゴロ依存度が高くなりすぎないように若干二遊間への確率を下げる) int percentValue = RandomCreater.GetPercentValue(); BattingGroundDirection direction; if (percentValue < 25) { // 二遊間:25% direction = BattingGroundDirection.Center; } else if (percentValue < 55) { // 一二塁間:30% direction = BattingGroundDirection.Right; } else if (percentValue < 85) { // 三遊間:30% direction = BattingGroundDirection.Left; } else if (percentValue < 90) { // 一塁ベース際:5% direction = BattingGroundDirection.Left; } else { // 三塁ベース際:10% direction = BattingGroundDirection.ThirdBase; } // 特殊能力「プルヒッター」は超引っ張りの確率アップ if (GameData.CurrentBatter.Player.FielderAbility.SpecialFilederAbility == SpecialFilederAbility.PullHitter && RandomCreater.GetPercentValue() < 5) { direction = BattingGroundDirection.ThirdBase; } // 右打ちの場合は一二塁間に打つ if (GameData.CurrentBattingSwingData.BattingSwingKind == BattingSwingKind.RightBatting) { direction = BattingGroundDirection.Right; } // エンドランの場合は一二塁間か三遊間に打つ if (GameData.CurrentBattingSwingData.BattingSwingKind == BattingSwingKind.EndRun) { direction = RandomCreater.GetBool() ? BattingGroundDirection.Right : BattingGroundDirection.Left; } // デバッグ用に打球方向が指定されている場合はその方向に設定する if (DebugBattingDirection != BattingGroundDirection.Invalid) { direction = DebugBattingDirection; } // 方向ごとに角度の算出 double angle; switch (direction) { case BattingGroundDirection.Center: // 二遊間(センター返し)約0度 // (二遊間は安打になりやすいので角度の誤差を大きくする) errorValue += Constants.BattedBollSideDirectionErrorByCenter; angle = Vector.GetAngle(GameData.Defense4Point, GameData.Defense6Point); break; case BattingGroundDirection.Right: // 一二塁間(流し打ち)約22.5度 angle = Vector.GetAngle(GameData.Defense3Point, GameData.Defense4Point); break; case BattingGroundDirection.Left: // 三遊間(引っ張り)約22.5度 angle = Vector.GetAngle(GameData.Defense5Point, GameData.Defense6Point); break; case BattingGroundDirection.FirstBase: // 低確率で一塁線(超流し打ち)約45度 angle = 45; break; case BattingGroundDirection.ThirdBase: // 低確率で三塁線(超引っ張り)約45度 // (引っ張ることで打球速度が上がるため安打になりやすく長打にもなりやすい) angle = -45; break; default: throw new Exception("Invalid BattingGroundDirection"); } // 角度の誤差の取得 int errorDirection = RandomCreater.GetRandomValue(errorValue * (-1), errorValue); // 超流し打ちか超引っ張りの場合はファウルにならず誤差も小さい if (direction == BattingGroundDirection.FirstBase) { errorDirection = Math.Abs(errorDirection / 2) * (-1); } else if (direction == BattingGroundDirection.ThirdBase) { errorDirection = Math.Abs(errorDirection / 2); } // 角度の設定 battedBoll.SideDirection = angle + errorDirection; // 左打者なら角度を反転する if (memberAbility.GameMember.Player.IsLeftBox) { battedBoll.SideDirection *= -1; } } else { // バントの場合の打球方向 // 打球方向(左右)は一塁線か三塁線を狙う // 智力が一定以上ある場合のみ、角度の誤差が小さくなる int errorValue = (int)GameMemberAbility.GetLowerValueOverBaseline(memberAbility.Wisdom, Constants.MaxBuntBollSideDirectionError); errorValue += Constants.MinBattedBollSideDirectionError; int errorDirection = RandomCreater.GetRandomValue(0, errorValue); // 状況によって打球方向を決定する bool firstBaseDirection = DecideDirectionForBunt(); if (firstBaseDirection) { // 一塁線 battedBoll.SideDirection = 45 - errorDirection; } else { // 三塁線 battedBoll.SideDirection = -45 + errorDirection; } } } } #endregion #region 角度により打球速度補正 // 左右の角度によって打球速度を変更 // (バントの場合は補正しない) if (!GameData.CurrentBattingBunt) { if ((!memberAbility.GameMember.Player.IsLeftBox && battedBoll.SideDirection > 0) || (memberAbility.GameMember.Player.IsLeftBox && battedBoll.SideDirection < 0)) { // 流し打ちによる減速 if (GameData.CurrentBatter.Player.FielderAbility.SpecialFilederAbility != SpecialFilederAbility.HitTheOtherWay) { // 「流し打ち○」の特殊能力がない場合は減速 battedBoll.Speed *= 1.0 - ((double)Math.Abs(battedBoll.SideDirection) / 45) * Constants.BattedBollSpeedDownRasioBySideDirection; } } else { // 引っ張りよる速度増加 battedBoll.Speed *= 1.0 + ((double)Math.Abs(battedBoll.SideDirection) / 45) * Constants.BattedBollSpeedUpRasioBySideDirection; } } #endregion #region 異常値にならないように補正 // 打球速度を最小値以上に補正 battedBoll.Speed = Math.Max(battedBoll.Speed, Constants.MinBattedBollSpeed); // 事前にファウルと判定しない場合はファウルにしない if (!isFoulDirection) { battedBoll.SideDirection = Math.Min(45, battedBoll.SideDirection); battedBoll.SideDirection = Math.Max(-45, battedBoll.SideDirection); } #endregion return battedBoll; }
/// <summary> /// 投手が実際に投げる投球データを取得 /// </summary> /// <param name="requestBall">要求する投球(コースと球種のみ設定)</param> /// <returns>実際に投げる投球データ</returns> private PitchingBallData GetActualPitchBoll(PitchingBallData requestBall) { GameMemberAbility memberAbility = GameData.CurrentPitcherAbirity; PitchingBallData actualBall = new PitchingBallData(); // 球種は要求通り actualBall.IsFastBall = requestBall.IsFastBall; // 球速 // (直球はそのまま、変化球は0) actualBall.BollSpeed = memberAbility.Fastball; if (!actualBall.IsFastBall) { actualBall.BollSpeed = 0; } // 変化球のキレ // (変化球はそのまま、直球は0) actualBall.BreakingAmount = memberAbility.Breakingball; if (actualBall.IsFastBall) { actualBall.BreakingAmount = 0; } #region コースの指定 { // 失投しない確率=(固定40%)+(変動40%) // (例)制球 8: 40%+26.0%=66.0% // 制球12: 40%+34.0%=74.0% // (能力最大でも失投がないと配球が単調になるため20%は失投する) // (以前は制球と球威の差でさらに変動させていたが制球の効果が高くなりすぎるため削除) double abilityValue = Constants.MaxAbilityValue * 4 / 10 + memberAbility.Control * 4 / 10; // 失投の有無を確率で判定(ウエストは失投しない) int failDelta; if (memberAbility.JudgByRandom(abilityValue, out failDelta) || requestBall.PitchingCourse == PitchingCourse.PitchOut) { // 狙い通りのコースを指定 actualBall.PitchingCourse = requestBall.PitchingCourse; } else { // 失投した場合、暴投→失投レベル2→失投レベル1の順に確率判定 int mistakeLevel; bool inOrOut = RandomCreater.GetPercentValue() < Constants.PitchingCourceMistakePercent; double percent = 5 + GameMemberAbility.GetLowerValue(memberAbility.Control, 15); if (RandomCreater.GetPercentValue() < percent) { // 暴投(制球8で10%程度) mistakeLevel = 5; inOrOut = false; } else { percent = 15 + GameMemberAbility.GetLowerValue(memberAbility.Control, 45); if (RandomCreater.GetPercentValue() < percent) { // 失投レベル2(制球8で30%程度) mistakeLevel = 2; } else { // 失投レベル1 mistakeLevel = 1; } } // 失投レベルに応じて投球コースを設定 actualBall.PitchingCourse = MistakeControl(requestBall.PitchingCourse, mistakeLevel, inOrOut); // 失投レベルを投球データに格納 if (inOrOut) { actualBall.MistakeLevel = mistakeLevel; } else { actualBall.MistakeLevel = mistakeLevel * (-1); } } // 投球コースの良さの設定 actualBall.CourseValue = ConvertCourseToAbilityValue(actualBall.PitchingCourse, memberAbility.Control, actualBall.MistakeLevel == 0); } #endregion #region 前回の球種との違いによる補正 // 前回の球種との違いによる補正 if (actualBall.PitchingCourse != PitchingCourse.PitchOut) { // 球種変更による補正値を球速か変化量に加算する double offset = GetOffsetValueByChabgeBollKind(GameData, actualBall.IsFastBall); if (actualBall.IsFastBall) { actualBall.BollSpeed += offset; } else { actualBall.BreakingAmount += offset; } // 前回球種と連続球数の更新 if (GameData.ContinueingSameBollCount == 0 || GameData.PreviousBollKind != actualBall.IsFastBall) { // 初球または前回と異なる球種の場合、今回の球種を設定 GameData.PreviousBollKind = actualBall.IsFastBall; GameData.ContinueingSameBollCount = 1; } else { // 前回と同じ球種の場合、連続球数を加算 GameData.ContinueingSameBollCount++; } } else { // ウエストの場合はリセットする // (CPUはウエストを活用しないのでユーザ向けの特別措置) GameData.ContinueingSameBollCount = 0; } #endregion // 実際の投球データを返す return actualBall; }