/// <summary> /// Draws a curve between startCoordinate and endCoordinate of movement according to the max offset, /// specified by amp. Drawn using a sine function with rotation /// Note that both Positions and slope have length of number of intervals + 1. This is for drawing purpose only. /// </summary> /// <param name="movement"></param> /// <param name="bpm"></param> /// <param name="amp"></param> public Function(Movement movement, int bpm, float amp) { this.movement = movement; Size = (int)((movement.endBeat - movement.startBeat + 1) / (float)bpm * 60 / INTERVAL_TIME) * 8; Positions = new Vector2[Size + 1]; // Change coordinates so that (0,0) is bottom left // Original start and ending positions specified by XML Vector2 oStartPos = new Vector2(movement.startCoordinate.X, movement.startCoordinate.Y); Vector2 oEndPos = new Vector2(movement.endCoordinate.X, movement.endCoordinate.Y); shiftPos = oStartPos; endPos = oEndPos - shiftPos; if (amp == 0) { isStraightLine = true; Position(); } else { Vector2 endPosPrime = new Vector2(Vector2.Distance(oStartPos, oEndPos), 0); Vector2 midPosPrime = new Vector2(endPosPrime.X / 2, 2 * amp); // Rotation angle double theta = Math.Atan((oEndPos.Y - oStartPos.Y) / (oEndPos.X - oStartPos.X)); if (oEndPos.X < oStartPos.X) theta += Math.PI; double midPosX = Math.Cos(theta) * midPosPrime.X - Math.Sin(theta) * midPosPrime.Y; double midPosY = Math.Sin(theta) * midPosPrime.X + Math.Cos(theta) * midPosPrime.Y; midPos = new Vector2((float)midPosX, (float)midPosY); Position(); } }
private bool Expired(Movement m) { return m.fadeBeat < current_beat; }
public override void Update(GameTime gameTime) { if (CountDownDone() && !watch.IsRunning) { Start(); } if (satisfaction.maxAge == 0 ) { if (!failed) { MediaPlayer.Stop(); distorted.Stop(); MediaPlayer.IsMuted = false; MediaPlayer.Play(LevelFail); } failed = true; } else { satisfaction.Update(gameTime); // Adjusts volume Keys key = gameState.Input.Key; if (key != Keys.None) { if (key == Keys.A) { volume = MathHelper.Clamp(volume + 1, 1, 10); } else if (key == Keys.Z) { volume = MathHelper.Clamp(volume - 1, 1, 10); } scaledVol = (float)(0.524 * Math.Pow(Math.E, volume / 10) - 0.425); // exponential scale if (MediaPlayer.IsMuted) distorted.Volume = scaledVol; else MediaPlayer.Volume = scaledVol; } //watch = watch.Add(gameTime.ElapsedGameTime); current_beat = beat_sum + (int)Math.Round((float)watch.ElapsedMilliseconds / beatTime); bool newMovement = false; if (current_beat > last_beat) // new beat { last_beat = current_beat; if (current_act != null && current_act.endBeat < current_beat) { current_act = null; } LinkedListNode<Movement> checkMove = actionList.First; drawSet.RemoveWhere(Expired); while (checkMove != null) { if (checkMove.Value.showBeat == current_beat) { drawSet.Add(checkMove.Value); checkMove = checkMove.Next; } else if (checkMove.Value.showBeat > current_beat) break; else checkMove = checkMove.Next; } do { // check and remove the head of the list if (actionList.First != null && actionList.First.Value.startBeat == current_beat) { current_act = actionList.First.Value; if (current_act.myType != Movement.Types.Control) { actionList.RemoveFirst(); c++; newMovement = true; } else { beat_sum = current_beat; accumulated_ms = accumulated_ms + (int)watch.ElapsedMilliseconds; beatTime = 60000 / current_act.BPM; actionList.RemoveFirst(); watch.Restart(); } } else break; } while (true); if (current_act != null) { Movement.Types type = current_act.myType; float score = moveEval.Accuracy(current_act, buffer, gameTime); gainedScore = (int)(score * 10); // comboOn = moveEval.CurrentMovement !=null && moveEval.CurrentMovement.myType == Movement.Types.Wave ? gainedScore > 0 : true; // Adjusts age of satisfaction queue if (gainedScore < 0) { if (moveEval.CurrentMovement.myType == Movement.Types.Wave ) failCount++; if (satisfaction.maxAge < 5) satisfaction.maxAge = 0; else satisfaction.maxAge = Math.Max(4, satisfaction.maxAge - AGE_DECR); if (failCount == 3 && current_act.myType == Movement.Types.Wave) { Random rand = new Random(); BrokenStrings[rand.Next(3)].Play(); } else { if (failCount >= 5) { MediaPlayer.IsMuted = true; distorted.Volume = scaledVol; } } } else { failCount = 0; satisfaction.maxAge = Math.Min(SatisfactionQueue.MAX_AGE, satisfaction.maxAge + AGE_INCR); MediaPlayer.IsMuted = false; distorted.Volume = 0.0f; } moveEval.Update(current_act, score, (newMovement || actionList.Count == 0), gameTime, this); /* Keep the combo on if it is now Wave and the most recent gainedScore is greater than 0 (i.e. success continues), * or if combo is on before a Shake phase is entered, * otherwise break the combo. */ // comboOn = !(gainedScore < 0 && type == Movement.Types.Wave /* and last is also wave */); comboOn = gainedScore > 3 || (comboOn && gainedScore == 0); /** Add to combo count if it is now Wave and combo is on, * else if it is now Wave but combo is broken, reset count to 0, * else if combo is on but a Shape or Noop phase is entered, keep the count until next Wave. */ comboCount = comboOn && moveEval.CurrentMovement.myType == Movement.Types.Wave ? comboCount + 1 : (type == Movement.Types.Wave ? 0 : comboCount); if (comboCount > maxCombo) maxCombo = comboCount; // if (actionList.First != null) // prevents score from endlessly increasing /** Add gainedScore to current_score. * If there is a combo and the most recent score is non-negative (e.g. Shaking is succuessful), also add the current combo count to the score */ if (actionList.Count != 0) current_score += (gainedScore + (comboCount > 1 && gainedScore > 0 ? comboCount : 0)); current_score = Math.Max(0, current_score); if (newMovement) { buffer.Clear(); } } } } if (actionList.Count == 0 || failed) { untilClapping++; if (untilClapping == 150) ended = true; if (!failed && untilClapping == 150) { if (current_score < 1000) { SmallApplause.Play(); } else { LargeApplause.Play(); } } backToMenu++; } if (backToMenu >= 420) { //UnloadContent(); // doesn't seem to work, i.e. memory usage does not decrease if (!failed) { gameState.Score = current_score; gameState.Combo = (maxCombo > 1 ? maxCombo : 0); gameState.UpdateStats(); } else { gameState.Score = -1; gameState.Combo = -1; } gameState.CurrentScreen = gameState.PreviousScreen; } }
/** public bool Timing(InputBuffer inputs, Point p, bool start) { if (inputs.Count != 0) { Vector2 coords = new Vector2(p.X, GameEngine.HEIGHT - p.Y); Vector2 pos = (start ? inputs[0].Position : inputs[inputs.Count - 1].Position); // Console.WriteLine("coords is " + coords); // Console.WriteLine("pos is " + pos); // Console.WriteLine("difference in pos is " + Vector2.Distance(coords, pos)+"\n"); return Vector2.Distance(coords, pos) <= 100; } else { return false; } } */ /*Returns a floating number 0 to 1 which indicates how well the input is matching the movement */ public float Accuracy(Movement m, InputBuffer inputs, GameTime t) { int totalInput = inputs.Count; int correct = 0; if (CurrentMovement != null) { switch (CurrentMovement.myType) { case Movement.Types.Noop: return (totalInput > 20 ? -0.5f : 0.0f); case Movement.Types.Shake: if (totalInput < 20) { return 0.0f; } else { foreach (InputState state in inputs) { if (Math.Abs(state.Acceleration.X) > ACC_THRESHOLD || Math.Abs(state.Acceleration.Y) > ACC_THRESHOLD) { correct++; } } return (float)correct / totalInput / 2; } case Movement.Types.Wave: if (totalInput < 5) { return -0.3f; } else { Vector2 startPos = new Vector2(CurrentMovement.startCoordinate.X, CurrentMovement.startCoordinate.Y); Vector2 endPos = new Vector2(CurrentMovement.endCoordinate.X, CurrentMovement.endCoordinate.Y); float DIST_THRESHOLD = 0.55f * Vector2.Distance(startPos, endPos); Vector2[] slopes = CurrentMovement.f.Slope(totalInput - 1); float errorSum = 0.0f; float dist = Vector2.Distance(inputs[totalInput - 1].Position, inputs[0].Position); if (dist >= DIST_THRESHOLD) { for (int i = 1; i < totalInput; i++) { Vector2 normVel = Vector2.Normalize(inputs[i].Velocity); Vector2 slope = slopes[i]; errorSum += (normVel.X - slope.X) * (normVel.X - slope.X) + (normVel.Y - slope.Y) * (normVel.Y - slope.Y); } float rmsError = (float)Math.Sqrt((double)errorSum / (double)(totalInput - 1)); float accuracy = (1 - rmsError * MAGIC_WAVE_THRESHOLD); return (accuracy > FAIL_THRESHOLD ? accuracy : -0.3f); } else { return -0.3f; } } default: return 0.0f; } } else { return 0.0f; } }
public MovementEvaluator(Movement m) { CurrentMovement = m; }
public void Update(Movement m, float score, bool newMovement, GameTime t, PlayLevel level) { if (newMovement) // current movement is over, set state accordingly { //Debug.WriteLine("NEW MOVEMENT!"); // send score back to Movement if (CurrentMovement != null) { if (score < FAIL_THRESHOLD) { CurrentMovement.setState(Movement.States.Fail); } else if (score >= FAIL_THRESHOLD) { CurrentMovement.setState(Movement.States.Succeed); } else // no op { CurrentMovement.setState(Movement.States.None); } } CurrentMovement = m; // update movement } }