/// <summary> /// ランナーの進塁/帰塁/停止を設定する(進塁停止していたランナーは残り到達時間が0のため、こちらを用いる) /// </summary> /// <param name="runnerStatus">更新するランナー</param> private void UpdateRunnerStatusForForwardOrBack(RunnerStatus runnerStatus) { int remainingTimeToNextBase; if (runnerStatus.IsStop || runnerStatus.RemainingTimeToNextBase == 0) { // 残り到達時間が0であれば、塁間距離から次の塁までの走塁にかかる時間を取得して判定 remainingTimeToNextBase = GetRemainingTimeToNextBase(runnerStatus.Run, Constants.BaseDistance); } else { // 進塁中であれば残り到達時間をそのまま用いる remainingTimeToNextBase = runnerStatus.RemainingTimeToNextBase; } UpdateRunnerStatusForForwardOrBack(runnerStatus, remainingTimeToNextBase); }
/// <summary> /// ランナーの進塁/帰塁/停止を設定する /// </summary> /// <param name="runnerStatus">更新するランナー</param> /// <param name="remainingTimeToNextBase">次の塁への残り到達時間</param> private void UpdateRunnerStatusForForwardOrBack(RunnerStatus runnerStatus, int remainingTimeToNextBase) { // 帰塁中は何も更新しない if (runnerStatus.BackRun) { return; } // 次の塁の取得 int nextBase = runnerStatus.OverBaseCount + 1; // 前のランナーが次の塁にとどまるならば進塁しない(ホームは重複してもよい) if (GameData.AllRunnerStatus.Where(r => r.RunnerNumber > runnerStatus.RunnerNumber).Any( preRunner => preRunner.FinalTargetBase < (int)BaseKind.HomeBase && preRunner.FinalTargetBase <= nextBase)) { SetRunnerStatusForStopOrBack(runnerStatus); return; } // 盗塁・エンドランのランナーは、初回の判定時、次の塁までは必ず進塁する bool forwardRun = GameData.AllDefensePassedSecond == 0 && runnerStatus.IsSteal && nextBase == runnerStatus.RunnerNumber + 1; // 送球完了までの時間を取得 int throwFinishTime = GetThrowFinishTime(nextBase); // 余裕時間の取得(0より大きければ進塁可能、0以下なら進塁不可) int marginTime = throwFinishTime - remainingTimeToNextBase; // デバッグ用文字列の設定 runnerStatus.DebugInfo = string.Format("throwFinishTime={0}, remainingTimeToNextBase={1}", throwFinishTime, remainingTimeToNextBase); // 進塁するか智力で判定 // (盗塁・エンドランのランナーは、次の塁までは必ず進塁すると判定する) if (JudgeForwardRun(runnerStatus, runnerStatus.OverBaseCount + 1, marginTime) || forwardRun) { runnerStatus.RemainingTimeToNextBase = remainingTimeToNextBase; runnerStatus.IsStop = false; runnerStatus.BackRun = false; // 進塁する場合、どこまで進塁するか設定する runnerStatus.FinalTargetBase = GetFinalTargetBase(runnerStatus, marginTime); } else { // 進塁しない SetRunnerStatusForStopOrBack(runnerStatus); } }
/// <summary> /// ランナーを進塁しないように設定する(停止するか帰塁する) /// </summary> /// <param name="runnerStatus"></param> private void SetRunnerStatusForStopOrBack(RunnerStatus runnerStatus) { if (runnerStatus.RemainingTimeToNextBase <= 0 && !runnerStatus.NeedBackRun) { // 塁にとどまっているなら停止する runnerStatus.BackRun = false; runnerStatus.IsStop = true; LastRunnerStopTime = GameData.AllDefensePassedSecond; } else { // 塁間にいる場合は帰塁する(ただし一塁より前には帰塁できない) if (runnerStatus.OverBaseCount >= 1) { runnerStatus.IsStop = false; if (!runnerStatus.BackRun) { // もともと帰塁中でなければ残り到達時間を更新する runnerStatus.BackRun = true; int needTimeToNextBase = GetRemainingTimeToNextBase(runnerStatus.Run, Constants.BaseDistance); runnerStatus.RemainingTimeToNextBase = needTimeToNextBase - runnerStatus.RemainingTimeToNextBase; } } } // 先行するランナーに追いつこうとしている場合は帰塁させる foreach (RunnerStatus followRunner in GameData.AllRunnerStatus.Where(r => r.RunnerNumber < runnerStatus.RunnerNumber && r.TargetBase >= runnerStatus.TargetBase && !r.BackRun)) { SetRunnerStatusForStopOrBack(followRunner); } }
/// <summary> /// ランナーの更新(単位時間分移動させる) /// </summary> /// <param name="runnerStatus"></param> private void UpdateRunnerStatus(RunnerStatus runnerStatus) { // ランナーが存在しないか進塁ストップの場合は何もしない if (runnerStatus == null || runnerStatus.IsStop) { return; } // ランナーの移動(残り時間を減算) runnerStatus.RemainingTimeToNextBase -= Constants.PassSecondByTurn; // ランナーの現在位置の更新 MPoint targetPoint = GameData.GetBasePoint(runnerStatus.TargetBase); MPoint startPoint = GameData.GetBasePoint(runnerStatus.FromBase); Vector moveVoctor = targetPoint - startPoint; double progress = (double)(runnerStatus.AllTimeForBaseDistance - runnerStatus.RemainingTimeToNextBase) / runnerStatus.AllTimeForBaseDistance; runnerStatus.Point = startPoint + moveVoctor * progress; // 次の塁に到達したか if (runnerStatus.RemainingTimeToNextBase <= 0) { // 位置の更新(速度だけでは微妙にずれるので到達したときに補正) switch (runnerStatus.TargetBase) { case 1: runnerStatus.Point = Constants.PointFirstBase; break; case 2: runnerStatus.Point = Constants.PointSecondBase; break; case 3: runnerStatus.Point = Constants.PointThirdBase; break; case 4: runnerStatus.Point = Constants.PointHomeBase; break; default: throw new Exception("Invalid TargetBase next"); } // ファウル時、フライの落下前、フォースアウトで3アウトになりうる場合は本塁に到達したことにならない // (フライとして捕球されるか、チェンジになる可能性があるため) if (runnerStatus.TargetBase == 4) { // ファウルの場合は本塁到達不可 if (IsFoul) { return; } // フライの落下前は本塁到達不可 if (GameData.IsBattedBollFlying) { return; } // フォースアウトで3アウトになりうる場合は本塁に到達したことならない int forceOutCount = 0; if (IsForceOut(1)) forceOutCount++; if (IsForceOut(2)) forceOutCount++; if (IsForceOut(3)) forceOutCount++; if (forceOutCount + GameData.OutCount >= 3) { return; } } // タッチアウト待ちか if (GameData.DefenseTermKind == DefenseTermKind.WaitingRunner) { // 到達した塁がタッチアウト待ちの塁か if (runnerStatus.TargetBase == ThrowTargetBase) { // タッチアウトになる SetOutRunner(runnerStatus, ThrowPosition); // 守備側が他の塁へ送球するか判定 JudgeAnotherThrowTaget(); return; } } // ここまで来たら次の塁に到達完了 if (!runnerStatus.BackRun) { // 進塁の場合は通過したベース数を加算 runnerStatus.OverBaseCount++; if (runnerStatus.OverBaseCount >= 4) { // ホームに到達した場合は得点 AddScore(runnerStatus.RunnerNumber); } else { // ホーム以外に進塁した場合は、次の塁に進むか判定 UpdateRunnerStatusForForwardOrBack(runnerStatus); } } else { // 帰塁中の場合 // さらに帰塁する必要があるか判定 if (runnerStatus.NeedBackRun && runnerStatus.TargetBase != runnerStatus.RunnerNumber) { // さらに帰塁する必要があれば、かかる時間を設定 runnerStatus.OverBaseCount--; runnerStatus.RemainingTimeToNextBase = GetRemainingTimeToNextBase(runnerStatus.Run, Constants.BaseDistance); } else { // 元の塁まで戻ってきたら停止 runnerStatus.NeedBackRun = false; runnerStatus.BackRun = false; runnerStatus.IsStop = true; LastRunnerStopTime = GameData.AllDefensePassedSecond; // 帰塁完了した時点ですでにフライが落下後であれば進塁の最判断 if (!GameData.IsBattedBollFlying) { UpdateRunnerStatusForForwardOrBack(runnerStatus); } } } } }
/// <summary> /// 指定したランナーをアウトにする /// </summary> /// <param name="runnerStatus">アウトにするランナー</param> /// <param name="defensePosition">アウトにする守備位置</param> /// <param name="isAssistOut">捕殺か(捕殺ならtrue、刺殺ならfalse)</param> private void SetOutRunner(RunnerStatus runnerStatus, DefensePosition defensePosition, bool isAssistOut = true) { // アウトカウントの加算 CurrentBatterOutCount++; // 併殺のためのアウトカウント加算 // ゴロであり、元の塁か次の塁でアウトになった場合のみカウント // (ゴロで元の塁でアウトになるのはレアケースだが一応考慮しておく) // (フライアウト、帰塁できないアウト、オーバーランはカウントしない) if (!CurrentFlyOut && (runnerStatus.TargetBase == runnerStatus.RunnerNumber || runnerStatus.TargetBase == runnerStatus.RunnerNumber + 1)) { CurrentBatterForceOutCount++; } // 打撃結果の設定 SetBattingResultByOut(defensePosition); // 指定したランナーをアウトにする GameData.SetOutRunner(runnerStatus.RunnerNumber); // 捕殺・刺殺の加算 GameData.GetDefenseMember(defensePosition).AddOutCount(isAssistOut); // アウトのイベント発行 if (JudgedSafeOrOut != null) { JudgedSafeOrOut(false); } }
/// <summary> /// ランナーが進塁するかしないか判定する /// </summary> /// <param name="runnerStatus">対象ランナー</param> /// <param name="targetBase">次の塁</param> /// <param name="marginTime">誤判断せず正しく判断できる時間</param> /// <returns>進塁するか</returns> private bool JudgeForwardRun(RunnerStatus runnerStatus, int targetBase, int marginTime) { // ランナー初期化の時点で、パスボールでなく2アウトであれば、元の塁から次の塁までは必ず進塁する if (GameData.AllDefensePassedSecond == 0 && IsBattedBollMode && GameData.OutCount == 2 && targetBase == runnerStatus.RunnerNumber + 1) { return true; } // フォースアウト対象であれば、無条件で進塁する // (FinalTargetBaseの判定も行うため、ランナーの元の塁も判定に含める) if (IsForceOut(targetBase) && targetBase == runnerStatus.RunnerNumber + 1) { return true; } // 実際に進塁可能かどうかを元に判定を分岐 GameMemberAbility memberAbility = runnerStatus.MemberAbility; if (marginTime <= 0) { // 進塁できない場合 // アホでも、試合バランスを維持するために無謀な進塁はしない // ただし、二死で捕逸以外でのホームへの進塁のみ無謀に暴走する(プロ野球でもよくある) // (捕逸でホームで刺されることがよくあるため、捕逸は暴走しないようにしている) if (GameData.OutCount == 2 && targetBase == 4 && !GameData.LatestPitchingBollResult.BattedBoll.IsPassBoll && !JudgeCorrectRun(memberAbility, marginTime)) { // 無謀に進塁 return true; } else { // 正しく停止 return false; } } else { // 進塁できる場合 // 賢いほど、ぎりぎり間にあう状態で積極的に進塁する // (アホだと、せっかくのチャンスに進塁しない) // ただし、二死でホームへの進塁時は進塁する if (JudgeCorrectRun(memberAbility, marginTime) || (GameData.OutCount == 2 && targetBase == 4)) { // 正しく進塁 return true; } else { // 進塁できるのに停止 return false; } } }
/// <summary> /// 打球を元に、ランナーの進塁/帰塁を設定する /// (進塁の場合は最終的に到達予定の塁も設定する) /// </summary> /// <param name="runnerStatus">ランナー状態</param> private void InitializeRunnerStatus(RunnerStatus runnerStatus) { // ファウル方向へのフライの場合は進塁しない if (GameData.LatestPitchingBollResult.BattedBoll.IsFoulDirection && GameData.LatestPitchingBollResult.BattedBoll.DropTime > 0) { SetRunnerStatusForStopOrBack(runnerStatus); return; } // 正しく判断可能な余裕時間を算出する // (この値より大きい余裕時間であれば、正しくフライがキャッチされることを判断できる) int correctJudgeTime = (int)GameMemberAbility.GetLowerValue(runnerStatus.Wisdom, Constants.MaxWrongJudgeTimeForFlyCatch); // フライキャッチされるかで分岐 if (CanCatchFly && MoveDropPointDeltaTime >= correctJudgeTime && GameData.OutCount < 2) { // フライキャッチされる(と判断した)場合 // 賢いほど、ぎりぎりでフライを捕球した場合でも前の塁に戻る // 2アウトならばフライでも帰塁しない if (runnerStatus.RunnerNumber != 0) { // 打者ランナーはフライで取られる場合もとりあえず一塁に進むため、何もしない SetRunnerStatusForStopOrBack(runnerStatus); } } else { // フライキャッチされない(と判断した)場合 // 次の塁までかかる残り時間を元に、進塁/帰塁と最終到達予定塁を設定する UpdateRunnerStatusForForwardOrBack(runnerStatus); } }
/// <summary> /// 最終到達予定の塁を取得する /// </summary> /// <param name="runnerStatus"></param> /// <param name="marginTime"></param> /// <returns></returns> private int GetFinalTargetBase(RunnerStatus runnerStatus, int marginTime) { Assertion.Assert(!runnerStatus.IsStop && !runnerStatus.BackRun, "!runnerStatus.IsStop && !runnerStatus.BackRun"); // 現在の塁からどこまで進塁可能か判断する int nextBase = runnerStatus.TargetBase; int finalTargetBase = nextBase; for (int i = nextBase + 1; i <= 4; i++) { // 次の次の塁まで到達するのにかかる時間を余裕時間から減算する marginTime -= GetRemainingTimeToNextBase(runnerStatus.Run, Constants.BaseDistance); // 進塁可能か判定する if (JudgeForwardRun(runnerStatus, i, marginTime)) { finalTargetBase = i; } else { break; } } return finalTargetBase; }
/// <summary> /// 指定したランナー情報の設定 /// (この時点ではランナーメンバは更新せず打席終了時に更新する) /// </summary> /// <param name="runnerNumber"></param> /// <param name="runnerStatus"></param> public void SetRunnerStatus(int runnerNumber, RunnerStatus runnerStatus) { switch (runnerNumber) { case 0: BatterRunnerStatus = runnerStatus; break; case 1: FirstRunnerStatus = runnerStatus; break; case 2: SecondRunnerStatus = runnerStatus; break; case 3: ThirdRunnerStatus = runnerStatus; break; default: throw new Exception("Invalid RunnerNumber"); } }