private void SetupEnergyAndTime(Game game, double energyLossForCutting, double energyGainPerConnectorBroken) { var elapsed = TimeSpan.Zero; var living = new LifetimeSource(); living.Lifetime.WhenDead(() => { if (Best == elapsed) { LabelTitle.Text = "New High Score! Try Again?"; LabelTitle.Foreground = new SolidColorBrush(Colors.Green); } else { LabelTitle.Text = "Game Over! Try Again?"; LabelTitle.Foreground = new SolidColorBrush(Colors.Red); } MenuPanel.Visibility = Visibility.Visible; }, game.Life); // show energy status var gains = 0.0; var loses = 0.0; var energy = 50.0; var maxEnergy = 50.0; game.LoopActions.Add(step => { // energy decays faster and faster over time var t = step.TimeStep.TotalSeconds; energy -= t * Math.Log(elapsed.TotalSeconds / 15 + 1).Max(1); // quickly decrease the size of the red/blue bars that show changes gains -= t * 5; loses -= t * 5; gains *= Math.Pow(0.01, t); loses *= Math.Pow(0.01, t); // keep everything reasonable gains = gains.Clamp(0, 10); loses = loses.Clamp(0, 10); energy = energy.Clamp(0, maxEnergy); // "draw" energy var w = canvas.ActualWidth; BarEnergy.Fill = new SolidColorBrush(Colors.Yellow.LerpTo(Colors.Green, (energy * 2 / maxEnergy).Clamp(0, 1))); BarLoses.Width = ((energy + loses) / maxEnergy * w).Clamp(0, w); BarGains.Width = (energy / maxEnergy * w).Clamp(0, w); BarEnergy.Width = ((energy - gains) / maxEnergy * w).Clamp(0, w); // hit 0 energy? game over if (energy == 0) living.EndLifetime(); }, game.Life); // track energy changes due to connectors dying game.Connectors.CurrentAndFutureItems().Subscribe(e => e.Lifetime.WhenDead(() => { // breaking connectors gain you some energy if (living.Lifetime.IsMortal) energy += energyGainPerConnectorBroken; gains += energyGainPerConnectorBroken; // but making cuts costs energy if (e.Value.CutPoint != null) { energy -= energyLossForCutting; loses += energyLossForCutting; } }), game.Life); // track times game.LoopActions.Add(step => { // advance time if (living.Lifetime.IsMortal) elapsed += step.TimeStep; Best = Best.Max(elapsed); // show time TimeLabel.Text = String.Format("Time: {0:0.0}s", elapsed.TotalSeconds); // show best time (green when making best time) TimeBest.Background = new SolidColorBrush( Best == elapsed ? Color.FromArgb(128, 0, 255, 0) : Colors.Transparent); TimeBest.Text = String.Format("Best: {0:0.0}s", Best.TotalSeconds); }, game.Life); }
private void SetupAndRunGame(Game game, bool initial) { // controls added to this collection should be displayed on the canvas until they perish var controls = new PerishableCollection<UIElement>(); controls.CurrentAndFutureItems().Subscribe( e => { canvas.Children.Add(e.Value); e.Lifetime.WhenDead(() => canvas.Children.Remove(e.Value)); }, game.Life); // balls should move and bounce off borders game.SetupMoveAndBounceBalls( playArea: () => new Rect(0, 0, canvas.ActualWidth, canvas.ActualHeight)); // connected balls should be be gently tugged towards each other game.SetupAttractBalls( deadRadius: 50, accellerationPerSecondChild: 10, accellerationPerSecondParent: 5); // balls should periodically spawn dependent children game.SetupPeriodicChildSpawning( expectedPerBallPerSecond: 0.2, maxChildrenPerBall: 2, maxGeneration: 5); // balls should be drawn using ellipse controls and have death animations game.SetupDrawBallsAsControls( controls, deathFadeOutDuration: 800.Milliseconds(), deathFinalRadiusFactor: 3); // ball connectors should be drawn using line controls and have cut and death animations game.SetupDrawConnectorsAsControls( controls, deathFadeDuration: 800.Milliseconds(), deathFinalThicknessFactor: 6, propagateBangColor: Colors.Green, propagateBangDuration: 400.Milliseconds(), propagateBangRotationsPerSecond: 3, propagateBangMaxRadius: 10, cutBangColor: Colors.Red, cutBangDuration: 400.Milliseconds(), cutBangMaxRadius: 15); // connectors that touch the cursor should die SetupMouseCutter(game, controls); // text displays of game state should track that state if (!initial) SetupEnergyAndTime( game, energyLossForCutting: 2.5, energyGainPerConnectorBroken: 1); // there should be a few root balls to start with foreach (var repeat in 5.Range()) { game.SpawnBall(parent: new Ball { Pos = new Point(game.Rng.NextDouble()*canvas.ActualWidth, game.Rng.NextDouble()*canvas.ActualHeight), Radius = 10, Life = new LifetimeSource(), Hue = game.Rng.NextDouble()*3 }); } // run the game loop until the game is over game.Loop().ContinueWith(e => { // exceptions? }); }
private void SetupMouseCutter(Game game, PerishableCollection<UIElement> controls) { // create rectangle to center under mouse var rotater = new RotateTransform(); var translater = new TranslateTransform(); var mouseTarget = new Rectangle { Width = 10, Height = 10, Fill = new SolidColorBrush(Colors.Black), IsHitTestVisible = false, RenderTransform = new TransformGroup { Children = new TransformCollection { rotater, translater } }, Visibility = Visibility.Collapsed, RenderTransformOrigin = new Point(0.5, 0.5) }; controls.Add(mouseTarget, game.Life); // make the rectangle rotate rotater.BeginAnimation( RotateTransform.AngleProperty, new DoubleAnimation(0, 360, 1.Seconds()) { RepeatBehavior = RepeatBehavior.Forever }); // watch mouse position over canvas, keeping the rotating rectangle centered on it var liveMousePos = default(Point?); MouseEventHandler h = (sender, arg) => { mouseTarget.Visibility = Visibility.Visible; liveMousePos = arg.GetPosition(canvas); translater.X = liveMousePos.Value.X - mouseTarget.Width / 2; translater.Y = liveMousePos.Value.Y - mouseTarget.Height / 2; }; MouseEventHandler h2 = (sender, arg) => { mouseTarget.Visibility = Visibility.Collapsed; liveMousePos = null; }; canvas.MouseMove += h; canvas.MouseLeave += h2; game.Life.WhenDead(() => canvas.MouseMove -= h); game.Life.WhenDead(() => canvas.MouseLeave -= h2); // pass along mouse positions to the game game.SetupMouseCutter( controls, () => liveMousePos, cutTolerance: 5); }