/// <summary> /// Applies the <see cref="Result"/> of this <see cref="DrawableHitObject"/>, notifying responders such as /// the <see cref="ScoreProcessor"/> of the <see cref="JudgementResult"/>. /// </summary> /// <param name="application">The callback that applies changes to the <see cref="JudgementResult"/>.</param> protected void ApplyResult(Action <JudgementResult> application) { if (Result.HasResult) { throw new InvalidOperationException("Cannot apply result on a hitobject that already has a result."); } application?.Invoke(Result); if (!Result.HasResult) { throw new InvalidOperationException($"{GetType().ReadableName()} applied a {nameof(JudgementResult)} but did not update {nameof(JudgementResult.Type)}."); } if (!Result.Type.IsValidHitResult(Result.Judgement.MinResult, Result.Judgement.MaxResult)) { throw new InvalidOperationException( $"{GetType().ReadableName()} applied an invalid hit result (was: {Result.Type}, expected: [{Result.Judgement.MinResult} ... {Result.Judgement.MaxResult}])."); } Result.TimeOffset = Math.Min(MaximumJudgementOffset, Time.Current - HitObject.GetEndTime()); if (Result.HasResult) { updateState(Result.IsHit ? ArmedState.Hit : ArmedState.Miss); } OnNewResult?.Invoke(this, Result); }
/// <summary> /// Applies the <see cref="Result"/> of this <see cref="DrawableHitObject"/>, notifying responders such as /// the <see cref="ScoreProcessor"/> of the <see cref="JudgementResult"/>. /// </summary> /// <param name="application">The callback that applies changes to the <see cref="JudgementResult"/>.</param> protected void ApplyResult(Action <JudgementResult> application) { application?.Invoke(Result); if (!Result.HasResult) { throw new InvalidOperationException($"{GetType().ReadableName()} applied a {nameof(JudgementResult)} but did not update {nameof(JudgementResult.Type)}."); } judgementOccurred = true; // Ensure that the judgement is given a valid time offset, because this may not get set by the caller var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; Result.TimeOffset = Time.Current - endTime; switch (Result.Type) { case HitResult.None: break; case HitResult.Miss: State.Value = ArmedState.Miss; break; default: State.Value = ArmedState.Hit; break; } OnNewResult?.Invoke(this, Result); }
protected virtual void AddNested(DrawableHitObject h) { h.OnNewResult += (d, r) => OnNewResult?.Invoke(d, r); h.OnRevertResult += (d, r) => OnRevertResult?.Invoke(d, r); h.ApplyCustomUpdateState += (d, j) => ApplyCustomUpdateState?.Invoke(d, j); nestedHitObjects.Value.Add(h); }
private void addNested(DrawableHitObject hitObject) { // Todo: Exists for legacy purposes, can be removed 20200417 hitObject.OnNewResult += (d, r) => OnNewResult?.Invoke(d, r); hitObject.OnRevertResult += (d, r) => OnRevertResult?.Invoke(d, r); hitObject.ApplyCustomUpdateState += (d, j) => ApplyCustomUpdateState?.Invoke(d, j); nestedHitObjects.Value.Add(hitObject); }
/// <summary> /// Creates and adds the visual representation of a <see cref="TObject"/> to this <see cref="DrawableRuleset{TObject}"/>. /// </summary> /// <param name="hitObject">The <see cref="TObject"/> to add the visual representation for.</param> private void addRepresentation(TObject hitObject) { var drawableObject = GetVisualRepresentation(hitObject); if (drawableObject == null) { return; } drawableObject.OnNewResult += (_, r) => OnNewResult?.Invoke(r); drawableObject.OnRevertResult += (_, r) => OnRevertResult?.Invoke(r); Playfield.Add(drawableObject); }
private void apply(HitObject hitObject) { if (nestedHitObjects.IsValueCreated) { nestedHitObjects.Value.Clear(); ClearNestedHitObjects(); } foreach (var h in hitObject.NestedHitObjects) { var drawableNested = CreateNestedHitObject(h) ?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}."); drawableNested.OnNewResult += (d, r) => OnNewResult?.Invoke(d, r); drawableNested.OnRevertResult += (d, r) => OnRevertResult?.Invoke(d, r); drawableNested.ApplyCustomUpdateState += (d, j) => ApplyCustomUpdateState?.Invoke(d, j); nestedHitObjects.Value.Add(drawableNested); AddNestedHitObject(drawableNested); } }
private void onNewResult(DrawableHitObject drawableHitObject, JudgementResult result) => OnNewResult?.Invoke(drawableHitObject, result);
private SearchResult AlphaBetaSearch(Position position, int alpha, int beta, int depth, int reportdepth, int depthUp, ref List <Move> orderedMoves) { ulong hash = 0L; if (useCache) { // If we've already processed this position, use the saved evaluation hash = position.GetHash(); if (hashtable.ContainsKey(hash)) { hashLookups++; return(hashtable[hash]); } } // Evaluate the position int eval = EvaluatePosition(position, depthUp); // See if someone won if (Math.Abs(eval) > Constants.VictoryScore - 100) { return(new SearchResult() { Score = eval, PrimaryVariation = eval > 0 ? "BLUE-WINS" : "RED-WINS" }); } // See if we need to immediately stop searching if (timer.ElapsedMilliseconds >= timeLimitMilliseconds) { cutoff = true; return(new SearchResult()); } // We've reached the depth of our search, so return the heuristic evaluation of the position // Make sure we're evaluating after our opponent's last move (meaning it's our turn to move again) so that we calculate full move pairs if (depth <= 0 && position.PlayerToMove == initiatingPlayer) { return(new SearchResult() { Score = eval, PrimaryVariation = "" }); } bool maximizingPlayer = position.PlayerToMove == Player.Blue; SearchResult best = new SearchResult() { Score = maximizingPlayer ? int.MinValue : int.MaxValue }; List <Move> nullMoves = null; List <Move> moves; if (orderedMoves != null) { // Use an ordered list of moves based on the evaluations from a previous search depth moves = orderedMoves; } else { moves = position.GetValidMoves(); } // If we have no moves, return the evaluation of the position if (moves.Count == 0) { return(new SearchResult() { Score = eval, PrimaryVariation = "" }); } int movenum = 1; foreach (Move move in moves) { // Copy the board and make a move Position copy = position.Clone(); copy.MakeMove(move); // Don't repeat positions if (copy.LastMoveWasRepetition()) { continue; } // Store the current node for search reporting if (reportdepth > 0 && depthUp == 1) { levelOneNode = move.ToString(); } // Find opponents best counter move SearchResult child = AlphaBetaSearch(copy, alpha, beta, depth - 1, reportdepth - 1, depthUp + 1, ref nullMoves); // Store the evaluation for iterative deepening move ordering move.Evaluation = child.Score; if (maximizingPlayer) { if (child.Score > best.Score) { best.Score = child.Score; best.BestMove = move; best.PrimaryVariation = move.ToString() + " " + child.PrimaryVariation; } alpha = Math.Max(alpha, best.Score); if (beta <= alpha) { // Beta cutoff break; } } else { if (child.Score < best.Score) { best.Score = child.Score; best.BestMove = move; best.PrimaryVariation = move.ToString() + " " + child.PrimaryVariation; } beta = Math.Min(beta, best.Score); if (beta <= alpha) { // Alpha cutoff break; } } if (reportdepth > 0) { if (best == null) { best = new SearchResult(); } if (best.BestMove == null) { best.BestMove = new Move(); } // Report on the progress of our search OnNewResult?.Invoke(null, new SearchStatus() { BestMoveSoFar = best, SearchedNodes = evaluations, ElapsedMilliseconds = timer.ElapsedMilliseconds, CurrentMove = movenum++, TotalMoves = moves.Count, Depth = currentDepth, HashLookups = hashLookups, CurrentVariation = (depthUp != 1 ? levelOneNode + " " : "") + move.ToString() + " " + child.PrimaryVariation }); } } if (useCache) { if (!hashtable.ContainsKey(hash)) { hashtable.Add(hash, best); } } // Store the moves for the next depth (if using iterative deepening if (depthUp == 1) { orderedMoves = moves; } return(best); }