{"action":"created","issue":{"url":"https://api.github.com/repos/SodiumFRP/sodium/issues/94","repository_url":"https://api.github.com/repos/SodiumFRP/sodium","labels_url":"https://api.github.com/repos/SodiumFRP/sodium/issues/94/labels{/name}","comments_url":"https://api.github.com/repos/SodiumFRP/sodium/issues/94/comments","events_url":"https://api.github.com/repos/SodiumFRP/sodium/issues/94/events","html_url":"https://github.com/SodiumFRP/sodium/issues/94","id":144054334,"number":94,"title":"Learning Sodium: Moving two paddles in Pong (C#)","user":{"login":"RobertWalter83","id":5462981,"avatar_url":"https://avatars.githubusercontent.com/u/5462981?v=3","gravatar_id":"","url":"https://api.github.com/users/RobertWalter83","html_url":"https://github.com/RobertWalter83","followers_url":"https://api.github.com/users/RobertWalter83/followers","following_url":"https://api.github.com/users/RobertWalter83/following{/other_user}","gists_url":"https://api.github.com/users/RobertWalter83/gists{/gist_id}","starred_url":"https://api.github.com/users/RobertWalter83/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/RobertWalter83/subscriptions","organizations_url":"https://api.github.com/users/RobertWalter83/orgs","repos_url":"https://api.github.com/users/RobertWalter83/repos","events_url":"https://api.github.com/users/RobertWalter83/events{/privacy}","received_events_url":"https://api.github.com/users/RobertWalter83/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":8,"created_at":"2016-03-28T19:03:36Z","updated_at":"2016-03-29T08:31:34Z","closed_at":null,"body":"As suggested by Stephen, I'm putting this question in here since there is no dedicated user forum yet.\r\n\r\nI'm trying to start learning Sodium by implementing Pong, using the C# implementation. When I try to make my two paddles move based on player input, I run into several problems. Notice that I'm aware that there is a lot of potential to refactor this code, it is work in progress after all. Still, I'm welcoming feedback that illustrates any misuses of Sodium. \r\n\r\nIf I use Transaction.RunVoid instead of Transaction.Run, moving a paddle works only a few seconds before it will be \"stuck\". This strikes me as odd.\r\n\r\nIf I use both paddles simultaneously (let's say by pressing Key.Up and Key.W), both paddles stop moving when I release only one key (let's say Key.Up). This is because no KeyDown event is fired anymore, even though Key.W is still pressed). If I continue by pressing Key.Down (whilst still holding Key.W the entire time), both paddles continue moving (showing me that my Key caching logic is not entirely bogus). I realize this is an issue of System.Windows.Input, but I was wondering if you guys have any idea how to achieve the behavior I want (simultaneous movement of two objects). This would most likely solve the problem that every keydown event initially slowes down exisitng movements for a bit. \r\n\r\n```\r\npublic partial class MainWindow : Window\r\n{\r\n        const int dxBoard = 600;\r\n        const int dyBoard = 400;\r\n        const int dxBoardHalf = dxBoard/2;\r\n        const int dyBoardHalf = dyBoard/2;\r\n\r\n        private readonly IListener listener;\r\n        private readonly HashSet<Key> mpkey = new HashSet<Key>();\r\n        private static readonly HashSet<Key> mpkeyRelevant = new HashSet<Key> { Key.W, Key.S, Key.Down, Key.Up };\r\n\r\n        private event KeyEventHandler AllKeyDown;\r\n\r\n        public MainWindow()\r\n        {\r\n            InitializeComponent();\r\n            this.cvs.Width = dxBoard;\r\n            this.cvs.Height = dyBoard;\r\n            Rectangle rect1 = RectanglePlayer();\r\n            Rectangle rect2 = RectanglePlayer();\r\n            this.cvs.Children.Add(rect1);\r\n            this.cvs.Children.Add(rect2);\r\n\r\n            listener = Transaction.Run(() =>\r\n            {\r\n                Player player1 = new Player(20);\r\n                Player player2 = new Player(dxBoard - 20);\r\n                //Ball ball = new Ball(dxBoardHalf, dyBoardHalf, 0, 0);\r\n\r\n                this.AllKeyDown += player1.MovementHandler(Key.W, Key.S, dyBoardHalf);\r\n                this.AllKeyDown += player2.MovementHandler(Key.Up, Key.Down, dyBoardHalf);\r\n                \r\n                Canvas.SetLeft(rect1, player1.dx);\r\n                IListener l1 = player1.dy.Listen(dy => Canvas.SetTop(rect1, dy));\r\n\r\n                Canvas.SetLeft(rect2, player2.dx);\r\n                IListener l2 = player2.dy.Listen(dy => Canvas.SetTop(rect2, dy));\r\n\r\n                return new ImmutableCompositeListener(new [] {l1, l2});\r\n            });\r\n        }\r\n        \r\n        protected override void OnKeyDown(KeyEventArgs e)\r\n        {\r\n            base.OnKeyDown(e);\r\n            if (IsRelevant(e.Key) && !mpkey.Contains(e.Key))\r\n                mpkey.Add(e.Key);\r\n\r\n            foreach (Key key in mpkey)\r\n                OnAllKeyDown(new KeyEventArgs(e.KeyboardDevice, e.InputSource, e.Timestamp, key));\r\n        }\r\n\r\n        protected virtual void OnAllKeyDown(KeyEventArgs e)\r\n        {\r\n            KeyEventHandler handler = AllKeyDown;\r\n            if (handler != null)\r\n                handler(this, e);\r\n        }\r\n        \r\n        private bool IsRelevant(Key key)\r\n        {\r\n            return mpkeyRelevant.Contains(key);\r\n        }\r\n\r\n        protected override void OnKeyUp(KeyEventArgs e)\r\n        {\r\n            base.OnKeyUp(e);\r\n            if (mpkey.Contains(e.Key))\r\n                mpkey.Remove(e.Key);\r\n        }\r\n\r\n        public void Dispose()\r\n        {\r\n            using (this.listener) { }\r\n        }\r\n\r\n        private Rectangle RectanglePlayer()\r\n        {\r\n            return new Rectangle\r\n            {\r\n                Fill = new SolidColorBrush(Colors.White),\r\n                Stroke = new SolidColorBrush(Colors.White),\r\n                StrokeThickness = 1.0,\r\n                Width = 15,\r\n                Height = 50,\r\n                RenderTransform = new TranslateTransform(-7.5, -25)\r\n            };\r\n        }\r\n    }\r\n\r\n    internal class Player\r\n    {\r\n        internal readonly double dx;\r\n        internal Cell<int> dy;\r\n        internal StreamSink<int> vy;\r\n        \r\n        internal Player(int dx)\r\n        {\r\n            this.dx = dx;\r\n            this.dy = new CellLoop<int>();\r\n            this.vy = new StreamSink<int>();\r\n        }\r\n\r\n        internal KeyEventHandler MovementHandler(Key keyUp, Key keyDown, int initialPosition)\r\n        {\r\n            KeyEventHandler handler = (obj, args) =>\r\n            {\r\n                Console.WriteLine(args.Key);\r\n                if (args.Key == keyUp)\r\n                    this.vy.Send(-1);\r\n                else if (args.Key == keyDown)\r\n                    this.vy.Send(1);\r\n            };\r\n\r\n            this.dy = this.vy.Accum(initialPosition, (v, _dy) => (_dy + 5 * v).Clamp(35, 365));\r\n\r\n\r\n            return handler;\r\n        }\r\n    }\r\n//... Ball and Game classes\r\n}\r\n\r\n```"},"comment":{"url":"https://api.github.com/repos/SodiumFRP/sodium/issues/comments/202776965","html_url":"https://github.com/SodiumFRP/sodium/issues/94#issuecomment-202776965","issue_url":"https://api.github.com/repos/SodiumFRP/sodium/issues/94","id":202776965,"user":{"login":"Ziriax","id":2389359,"avatar_url":"https://avatars.githubusercontent.com/u/2389359?v=3","gravatar_id":"","url":"https://api.github.com/users/Ziriax","html_url":"https://github.com/Ziriax","followers_url":"https://api.github.com/users/Ziriax/followers","following_url":"https://api.github.com/users/Ziriax/following{/other_user}","gists_url":"https://api.github.com/users/Ziriax/gists{/gist_id}","starred_url":"https://api.github.com/users/Ziriax/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Ziriax/subscriptions","organizations_url":"https://api.github.com/users/Ziriax/orgs","repos_url":"https://api.github.com/users/Ziriax/repos","events_url":"https://api.github.com/users/Ziriax/events{/privacy}","received_events_url":"https://api.github.com/users/Ziriax/received_events","type":"User","site_admin":false},"created_at":"2016-03-29T08:31:34Z","updated_at":"2016-03-29T08:31:34Z","body":"Here's a quick example of two moving paddles in WPF and .NET 4.6.1\r\n\r\nI did not take the time to introduce constants, and I use a stupid Euler integral. Stephen uses quadratic polynomials and correct integrals :-)\r\n\r\nMainWindow.xaml\r\n```\r\n<Window x:Class=\"Pong.MainWindow\"\r\n        xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\r\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\r\n        xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\r\n        xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\r\n        mc:Ignorable=\"d\"\r\n        Title=\"Sodium Duopaddle\">\r\n</Window>\r\n``` \r\n\r\nMainWindows.xaml.cs\r\n```\r\nusing System;\r\nusing System.Windows;\r\nusing System.Windows.Input;\r\nusing System.Windows.Media;\r\nusing Sodium;\r\n\r\nnamespace Pong\r\n{\r\n\tpublic partial class MainWindow\r\n\t{\r\n\t\t[Flags]\r\n\t\tpublic enum PlayerKeyboardState\r\n\t\t{\r\n\t\t\tNone = 0,\r\n\t\t\tMoveUp = 1,\r\n\t\t\tMoveDown = 2\r\n\t\t}\r\n\r\n\t\tprivate readonly Pen _borderPen = new Pen(Brushes.DimGray, 4);\r\n\r\n\t\tprivate readonly Cell<Point> _cPlayerPos1;\r\n\t\tprivate readonly Cell<Point> _cPlayerPos2;\r\n\t\tprivate IDisposable _subscription;\r\n\r\n\t\tpublic MainWindow()\r\n\t\t{\r\n\t\t\tInitializeComponent();\r\n\r\n\t\t\tWindowStyle = WindowStyle.ToolWindow;\r\n\t\t\tResizeMode = ResizeMode.NoResize;\r\n\t\t\tBackground = Brushes.Transparent;\r\n\t\t\tSizeToContent = SizeToContent.WidthAndHeight;\r\n\t\t\tContent = new FrameworkElement\r\n\t\t\t{\r\n\t\t\t\tWidth = 640,\r\n\t\t\t\tHeight = 480\r\n\t\t\t};\r\n\r\n\t\t\tvar sKeyEvents = new StreamSink<KeyEventArgs>();\r\n\t\t\tvar sRenderEvents = new StreamSink<RenderingEventArgs>();\r\n\r\n\t\t\tKeyDown += (sender, args) => sKeyEvents.Send(args);\r\n\t\t\tKeyUp += (sender, args) => sKeyEvents.Send(args);\r\n\t\t\tCompositionTarget.Rendering += (sender, args) => sRenderEvents.Send((RenderingEventArgs) args);\r\n\r\n\t\t\tvar paddleVelocity = new Vector(0, 100);\r\n\t\t\tvar sTicks = sRenderEvents.Collect(TimeSpan.Zero, (re, t) => Tuple.Create(re.RenderingTime - t, re.RenderingTime));\r\n\t\t\t_cPlayerPos1 = PlayerPos(new Point(10, 220), paddleVelocity, Key.Q, Key.A, sKeyEvents, sTicks);\r\n\t\t\t_cPlayerPos2 = PlayerPos(new Point(610, 220), paddleVelocity, Key.Up, Key.Down, sKeyEvents, sTicks);\r\n\r\n\t\t\tvar sPlayerUpdates1 = Operational.Updates(_cPlayerPos1);\r\n\t\t\tvar sPlayerUpdates2 = Operational.Updates(_cPlayerPos2);\r\n\t\t\tvar sPlayerUpdates = sPlayerUpdates1.OrElse(sPlayerUpdates2);\r\n\r\n\t\t\t_subscription = sPlayerUpdates.Listen(_ => InvalidateVisual());\r\n\t\t}\r\n\r\n\t\tprotected override void OnClosed(EventArgs e)\r\n\t\t{\r\n\t\t\t_subscription?.Dispose();\r\n\t\t\t_subscription = null;\r\n\t\t\tbase.OnClosed(e);\r\n\t\t}\r\n\r\n\t\tprotected override void OnRender(DrawingContext dc)\r\n\t\t{\r\n\t\t\tvar pos1 = _cPlayerPos1.Sample();\r\n\t\t\tvar pos2 = _cPlayerPos2.Sample();\r\n\r\n\t\t\tdc.DrawRectangle(Brushes.Black, _borderPen, new Rect(2, 2, 640 - 4, 480 - 4));\r\n\r\n\t\t\tdc.DrawRoundedRectangle(Brushes.Red, null, new Rect(pos1, new Size(20, 60)), 5, 5);\r\n\t\t\tdc.DrawRoundedRectangle(Brushes.LimeGreen, null, new Rect(pos2, new Size(20, 60)), 5, 5);\r\n\t\t}\r\n\r\n\t\tpublic static PlayerKeyboardState Update(PlayerKeyboardState state, Key upKey, Key downKey, KeyEventArgs e)\r\n\t\t{\r\n\t\t\tvar flag =\r\n\t\t\t\te.Key == upKey ? PlayerKeyboardState.MoveUp : \r\n\t\t\t\te.Key == downKey ? PlayerKeyboardState.MoveDown : \r\n\t\t\t\t0;\r\n\r\n\t\t\treturn e.IsDown ? state | flag : state & ~flag;\r\n\t\t}\r\n\r\n\t\tpublic static int KeyStateToDirection(PlayerKeyboardState pks)\r\n\t\t{\r\n\t\t\treturn\r\n\t\t\t\tpks == PlayerKeyboardState.MoveUp ? -1 : \r\n\t\t\t\tpks == PlayerKeyboardState.MoveDown ? +1 : \r\n\t\t\t\t0;\r\n\t\t}\r\n\r\n\t\tpublic static Cell<Point> EulerIntegral(Point start, Cell<Vector> cVelocity, Stream<TimeSpan> sTicks)\r\n\t\t{\r\n\t\t\tvar steps = sTicks.Snapshot(cVelocity, (dt, v) => dt.TotalSeconds*v);\r\n\t\t\treturn steps.Accum(start, (dp, p) => p + dp);\r\n\t\t}\r\n\r\n\t\tpublic static Cell<Point> PlayerPos(Point startPos, Vector velocity, Key upKey, Key downKey,\r\n\t\t\tStream<KeyEventArgs> sKeyEvents, Stream<TimeSpan> sTicks)\r\n\t\t{\r\n\t\t\tvar cKeyState = sKeyEvents.Accum(PlayerKeyboardState.None, (e, s) => Update(s, upKey, downKey, e));\r\n\t\t\tvar cDirection = cKeyState.Map(KeyStateToDirection);\r\n\t\t\tvar cVelocity = cDirection.Map(d => d*velocity);\r\n\t\t\treturn EulerIntegral(startPos, cVelocity, sTicks);\r\n\t\t}\r\n\t}\r\n}\r\n```\r\n"}}