Browse Source

Adding new AutoPong sample for 3.8.1

Simon Jackson 3 years ago
parent
commit
ff46ee7b67

+ 36 - 0
AutoPong/AutoPong.Core/.config/dotnet-tools.json

@@ -0,0 +1,36 @@
+{
+  "version": 1,
+  "isRoot": true,
+  "tools": {
+    "dotnet-mgcb": {
+      "version": "3.8.1.303",
+      "commands": [
+        "mgcb"
+      ]
+    },
+    "dotnet-mgcb-editor": {
+      "version": "3.8.1.303",
+      "commands": [
+        "mgcb-editor"
+      ]
+    },
+    "dotnet-mgcb-editor-linux": {
+      "version": "3.8.1.303",
+      "commands": [
+        "mgcb-editor-linux"
+      ]
+    },
+    "dotnet-mgcb-editor-windows": {
+      "version": "3.8.1.303",
+      "commands": [
+        "mgcb-editor-windows"
+      ]
+    },
+    "dotnet-mgcb-editor-mac": {
+      "version": "3.8.1.303",
+      "commands": [
+        "mgcb-editor-mac"
+      ]
+    }
+  }
+}

+ 10 - 0
AutoPong/AutoPong.Core/AutoPong.Core.csproj

@@ -0,0 +1,10 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>net6.0</TargetFramework>
+  </PropertyGroup>
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.303">
+      <PrivateAssets>All</PrivateAssets>
+    </PackageReference>
+  </ItemGroup>
+</Project>

+ 245 - 0
AutoPong/AutoPong.Core/AutoPongGame.cs

@@ -0,0 +1,245 @@
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using System;
+using System.Runtime.Intrinsics.Arm;
+
+namespace AutoPong.DesktopGL
+{
+    public class AutoPongGame : Game
+    {
+        private GraphicsDeviceManager _graphics;
+        private SpriteBatch _spriteBatch;
+        private Point GameBounds = new Point(1280, 720); //window resolution
+
+        private Rectangle PaddleLeft;
+        private Rectangle PaddleRight;
+
+        private Rectangle Ball;
+        private Vector2 BallVelocity;
+        private Vector2 BallPosition;
+        private float BallSpeed = 15.0f;
+
+        public Texture2D Texture;
+        private Rectangle DrawRec = new Rectangle(0, 0, 3, 3);
+
+        private Random Rand = new Random();
+        private byte HitCounter = 0;
+
+        private int PointsLeft;
+        private int PointsRight;
+        private int PointsPerGame = 4;
+
+        private AudioSource SoundFX;
+        private int JingleCounter = 0;
+
+        public AutoPongGame()
+        {
+            _graphics = new GraphicsDeviceManager(this);
+            Content.RootDirectory = "Content";
+            IsMouseVisible = true;
+            _graphics.PreferredBackBufferWidth = GameBounds.X;
+            _graphics.PreferredBackBufferHeight = GameBounds.Y;
+        }
+
+        protected override void LoadContent()
+        {
+            _spriteBatch = new SpriteBatch(GraphicsDevice);
+
+            Reset();
+        }
+
+        protected override void Update(GameTime gameTime)
+        {
+#if !__IOS__
+            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
+                Exit();
+#endif
+            #region Update Ball
+
+            //limit how fast ball can move each frame
+            float maxVelocity = 1.5f;
+            if (BallVelocity.X > maxVelocity) { BallVelocity.X = maxVelocity; }
+            else if (BallVelocity.X < -maxVelocity) { BallVelocity.X = -maxVelocity; }
+            if (BallVelocity.Y > maxVelocity) { BallVelocity.Y = maxVelocity; }
+            else if (BallVelocity.Y < -maxVelocity) { BallVelocity.Y = -maxVelocity; }
+
+            //apply velocity to position
+            BallPosition.X += BallVelocity.X * BallSpeed;
+            BallPosition.Y += BallVelocity.Y * BallSpeed;
+
+            //check for collision with paddles
+            HitCounter++;
+            if (HitCounter > 10)
+            {
+                if (PaddleLeft.Intersects(Ball))
+                {
+                    BallVelocity.X *= -1;
+                    BallVelocity.Y *= 1.1f;
+                    HitCounter = 0;
+                    BallPosition.X = PaddleLeft.X + PaddleLeft.Width + 10;
+                    SoundFX.PlayWave(220.0f, 50, WaveType.Sin, 0.3f);
+                }
+                if (PaddleRight.Intersects(Ball))
+                {
+                    BallVelocity.X *= -1;
+                    BallVelocity.Y *= 1.1f;
+                    HitCounter = 0;
+                    BallPosition.X = PaddleRight.X - 10;
+                    SoundFX.PlayWave(220.0f, 50, WaveType.Sin, 0.3f);
+                }
+            }
+
+            //bounce on screen
+            if (BallPosition.X < 0) //point for right
+            {
+                BallPosition.X = 1;
+                BallVelocity.X *= -1;
+                PointsRight++;
+                SoundFX.PlayWave(440.0f, 50, WaveType.Square, 0.3f);
+            }
+            else if (BallPosition.X > GameBounds.X) //point for left
+            {
+                BallPosition.X = GameBounds.X - 1;
+                BallVelocity.X *= -1;
+                PointsLeft++;
+                SoundFX.PlayWave(440.0f, 50, WaveType.Square, 0.3f);
+            }
+
+            if (BallPosition.Y < 0 + 10) //limit to minimum Y pos
+            {
+                BallPosition.Y = 10 + 1;
+                BallVelocity.Y *= -(1 + Rand.Next(-100, 101) * 0.005f);
+            }
+            else if (BallPosition.Y > GameBounds.Y - 10) //limit to maximum Y pos
+            {
+                BallPosition.Y = GameBounds.Y - 11;
+                BallVelocity.Y *= -(1 + Rand.Next(-100, 101) * 0.005f);
+            }
+
+            #endregion
+
+            #region Simulate Left Paddle Input
+            {   //simple ai, not very good, moves random amount each frame
+                int amount = Rand.Next(0, 6);
+                int Paddle_Center = PaddleLeft.Y + PaddleLeft.Height / 2;
+                if (Paddle_Center < BallPosition.Y - 20) { PaddleLeft.Y += amount; }
+                else if (Paddle_Center > BallPosition.Y + 20) { PaddleLeft.Y -= amount; }
+                LimitPaddle(ref PaddleLeft);
+            }
+            #endregion Simulate Left Paddle Input
+
+            #region Simulate Right Paddle Input
+            {   //simple ai, better than left, moves % each frame
+                int Paddle_Center = PaddleRight.Y + PaddleRight.Height / 2;
+                if (Paddle_Center < BallPosition.Y - 20)
+                { PaddleRight.Y -= (int)((Paddle_Center - BallPosition.Y) * 0.08f); }
+                else if (Paddle_Center > BallPosition.Y + 20)
+                { PaddleRight.Y += (int)((BallPosition.Y - Paddle_Center) * 0.08f); }
+                LimitPaddle(ref PaddleRight);
+            }
+            #endregion Simulate Right Paddle Input
+
+            #region Check Win
+            //Check for win condition, reset
+            if (PointsLeft >= PointsPerGame) { Reset(); }
+            else if (PointsRight >= PointsPerGame) { Reset(); }
+            #endregion Check Win
+
+            #region Play Reset Jingle
+            //use jingle counter as a timeline to play notes
+            JingleCounter++;
+
+            int speed = 7;
+            if (JingleCounter == speed * 1) { SoundFX.PlayWave(440.0f, 100, WaveType.Sin, 0.2f); }
+            else if (JingleCounter == speed * 2) { SoundFX.PlayWave(523.25f, 100, WaveType.Sin, 0.2f); }
+            else if (JingleCounter == speed * 3) { SoundFX.PlayWave(659.25f, 100, WaveType.Sin, 0.2f); }
+            else if (JingleCounter == speed * 4) { SoundFX.PlayWave(783.99f, 100, WaveType.Sin, 0.2f); }
+            //only play this jingle once
+            else if (JingleCounter > speed * 4) { JingleCounter = int.MaxValue - 1; }
+            #endregion Play Reset Jingle
+
+            base.Update(gameTime);
+        }
+
+        protected override void Draw(GameTime gameTime)
+        {
+            GraphicsDevice.Clear(Color.CornflowerBlue);
+
+            _spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp);
+
+            //draw dots down center
+            int total = GameBounds.Y / 20;
+            for (int i = 0; i < total; i++)
+            {
+                DrawRectangle(_spriteBatch, new Rectangle(GameBounds.X / 2 - 4, 5 + (i * 20), 8, 8), Color.White * 0.2f);
+            }
+
+            //draw paddles
+            DrawRectangle(_spriteBatch, PaddleLeft, Color.White);
+            DrawRectangle(_spriteBatch, PaddleRight, Color.White);
+
+            //draw ball
+            Ball.X = (int)BallPosition.X;
+            Ball.Y = (int)BallPosition.Y;
+            DrawRectangle(_spriteBatch, Ball, Color.White);
+
+            //draw current game points
+            for (int i = 0; i < PointsLeft; i++)
+            {
+                DrawRectangle(_spriteBatch, new Rectangle((GameBounds.X / 2 - 25) - i * 12, 10, 10, 10), Color.White * 1.0f);
+            }
+            for (int i = 0; i < PointsRight; i++)
+            {
+                DrawRectangle(_spriteBatch, new Rectangle((GameBounds.X / 2 + 15) + i * 12, 10, 10, 10), Color.White * 1.0f);
+            }
+
+            _spriteBatch.End();
+
+            base.Draw(gameTime);
+        }
+
+        private void DrawRectangle(SpriteBatch sb, Rectangle Rec, Color color)
+        {
+            Vector2 pos = new Vector2(Rec.X, Rec.Y);
+            sb.Draw(Texture, pos, Rec,
+                color * 1.0f,
+                0, Vector2.Zero, 1.0f,
+                SpriteEffects.None, 0.00001f);
+        }
+
+        private void LimitPaddle(ref Rectangle Paddle)
+        {
+            //limit how far paddles can travel on Y axis so they dont exceed top or bottom
+            if (Paddle.Y < 10) { Paddle.Y = 10; }
+            else if (Paddle.Y + Paddle.Height > GameBounds.Y - 10)
+            { Paddle.Y = GameBounds.Y - 10 - Paddle.Height; }
+        }
+
+        private void Reset()
+        {
+            if (Texture == null)
+            {   //create texture to draw with if it does not exist
+                Texture = new Texture2D(_graphics.GraphicsDevice, 1, 1);
+                Texture.SetData<Color>(new Color[] { Color.White });
+            }
+
+            int PaddleHeight = 100;
+            PaddleLeft = new Rectangle(0 + 10, 150, 20, PaddleHeight);
+            PaddleRight = new Rectangle(GameBounds.X - 30, 150, 20, PaddleHeight);
+
+            BallPosition = new Vector2(GameBounds.X / 2, 200);
+            Ball = new Rectangle((int)BallPosition.X, (int)BallPosition.Y, 10, 10);
+            BallVelocity = new Vector2(1, 0.1f);
+
+            PointsLeft = 0; PointsRight = 0;
+            JingleCounter = 0;
+
+            //setup sound sources
+            if (SoundFX == null)
+            {
+                SoundFX = new AudioSource();
+            }
+        }
+    }
+}

+ 70 - 0
AutoPong/AutoPong.Core/Game/AudioSource.cs

@@ -0,0 +1,70 @@
+using Microsoft.Xna.Framework.Audio;
+using System;
+
+namespace AutoPong
+{
+    public class AudioSource
+    {
+        private int SampleRate = 48000;
+        private DynamicSoundEffectInstance DSEI;
+        private byte[] Buffer;
+        private int BufferSize;
+        private int TotalTime = 0;
+        static Random Rand = new Random();
+
+        public AudioSource()
+        {
+            DSEI = new DynamicSoundEffectInstance(SampleRate, AudioChannels.Mono);
+            BufferSize = DSEI.GetSampleSizeInBytes(TimeSpan.FromMilliseconds(500));
+            Buffer = new byte[BufferSize];
+            DSEI.Volume = 0.4f;
+            DSEI.IsLooped = false;
+        }
+
+        public void PlayWave(double freq, short durMS, WaveType Wt, float Volume)
+        {
+            DSEI.Stop();
+
+            BufferSize = DSEI.GetSampleSizeInBytes(TimeSpan.FromMilliseconds(durMS));
+            Buffer = new byte[BufferSize];
+
+            int size = BufferSize - 1;
+            for (int i = 0; i < size; i += 2)
+            {
+                double time = (double)TotalTime / (double)SampleRate;
+
+                short currentSample = 0;
+                switch (Wt)
+                {
+                    case WaveType.Sin:
+                        {
+                            currentSample = (short)(Math.Sin(2 * Math.PI * freq * time) * (double)short.MaxValue * Volume);
+                            break;
+                        }
+                    case WaveType.Tan:
+                        {
+                            currentSample = (short)(Math.Tan(2 * Math.PI * freq * time) * (double)short.MaxValue * Volume);
+                            break;
+                        }
+                    case WaveType.Square:
+                        {
+                            currentSample = (short)(Math.Sign(Math.Sin(2 * Math.PI * freq * time)) * (double)short.MaxValue * Volume);
+                            break;
+                        }
+                    case WaveType.Noise:
+                        {
+                            currentSample = (short)(Rand.Next(-short.MaxValue, short.MaxValue) * Volume);
+                            break;
+                        }
+                }
+
+                Buffer[i] = (byte)(currentSample & 0xFF);
+                Buffer[i + 1] = (byte)(currentSample >> 8);
+                TotalTime += 2;
+            }
+
+            DSEI.SubmitBuffer(Buffer);
+            DSEI.Play();
+        }
+    }
+}

+ 10 - 0
AutoPong/AutoPong.Core/Game/WaveType.cs

@@ -0,0 +1,10 @@
+namespace AutoPong
+{
+    public enum WaveType 
+    { 
+        Sin,
+        Tan,
+        Square,
+        Noise
+    }
+}

+ 31 - 0
AutoPong/AutoPong.DesktopGL.sln

@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.4.32912.340
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoPong.DesktopGL", "AutoPong.DesktopGL\AutoPong.DesktopGL.csproj", "{DD0539AD-0CA6-467C-9955-BA19FFC63CB2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoPong.Core", "AutoPong.Core\AutoPong.Core.csproj", "{8A06B930-BD4A-4495-948B-19B9B205A26D}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{DD0539AD-0CA6-467C-9955-BA19FFC63CB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{DD0539AD-0CA6-467C-9955-BA19FFC63CB2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{DD0539AD-0CA6-467C-9955-BA19FFC63CB2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{DD0539AD-0CA6-467C-9955-BA19FFC63CB2}.Release|Any CPU.Build.0 = Release|Any CPU
+		{8A06B930-BD4A-4495-948B-19B9B205A26D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{8A06B930-BD4A-4495-948B-19B9B205A26D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{8A06B930-BD4A-4495-948B-19B9B205A26D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{8A06B930-BD4A-4495-948B-19B9B205A26D}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {30AA8C87-3B33-446B-B943-F277DFCC7B07}
+	EndGlobalSection
+EndGlobal

+ 36 - 0
AutoPong/AutoPong.DesktopGL/.config/dotnet-tools.json

@@ -0,0 +1,36 @@
+{
+  "version": 1,
+  "isRoot": true,
+  "tools": {
+    "dotnet-mgcb": {
+      "version": "3.8.1.303",
+      "commands": [
+        "mgcb"
+      ]
+    },
+    "dotnet-mgcb-editor": {
+      "version": "3.8.1.303",
+      "commands": [
+        "mgcb-editor"
+      ]
+    },
+    "dotnet-mgcb-editor-linux": {
+      "version": "3.8.1.303",
+      "commands": [
+        "mgcb-editor-linux"
+      ]
+    },
+    "dotnet-mgcb-editor-windows": {
+      "version": "3.8.1.303",
+      "commands": [
+        "mgcb-editor-windows"
+      ]
+    },
+    "dotnet-mgcb-editor-mac": {
+      "version": "3.8.1.303",
+      "commands": [
+        "mgcb-editor-mac"
+      ]
+    }
+  }
+}

+ 32 - 0
AutoPong/AutoPong.DesktopGL/AutoPong.DesktopGL.csproj

@@ -0,0 +1,32 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>WinExe</OutputType>
+    <TargetFramework>net6.0</TargetFramework>
+    <RollForward>Major</RollForward>
+    <PublishReadyToRun>false</PublishReadyToRun>
+    <TieredCompilation>false</TieredCompilation>
+  </PropertyGroup>
+  <PropertyGroup>
+    <ApplicationManifest>app.manifest</ApplicationManifest>
+    <ApplicationIcon>Icon.ico</ApplicationIcon>
+  </PropertyGroup>
+  <ItemGroup>
+    <None Remove="Icon.ico" />
+    <None Remove="Icon.bmp" />
+  </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="Icon.ico" />
+    <EmbeddedResource Include="Icon.bmp" />
+  </ItemGroup>
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.303" />
+    <PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.1.303" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\AutoPong.Core\AutoPong.Core.csproj" />
+  </ItemGroup>
+  <Target Name="RestoreDotnetTools" BeforeTargets="Restore">
+    <Message Text="Restoring dotnet tools" Importance="High" />
+    <Exec Command="dotnet tool restore" />
+  </Target>
+</Project>

BIN
AutoPong/AutoPong.DesktopGL/Icon.bmp


BIN
AutoPong/AutoPong.DesktopGL/Icon.ico


+ 3 - 0
AutoPong/AutoPong.DesktopGL/Program.cs

@@ -0,0 +1,3 @@
+
+using var game = new AutoPong.DesktopGL.AutoPongGame();
+game.Run();

+ 43 - 0
AutoPong/AutoPong.DesktopGL/app.manifest

@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+  <assemblyIdentity version="1.0.0.0" name="AutoPong.DesktopGL"/>
+  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+    <security>
+      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
+        <requestedExecutionLevel  level="asInvoker" uiAccess="false" />
+      </requestedPrivileges>
+    </security>
+  </trustInfo>
+
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- A list of the Windows versions that this application has been tested on and is
+           is designed to work with. Uncomment the appropriate elements and Windows will 
+           automatically selected the most compatible environment. -->
+
+      <!-- Windows Vista -->
+      <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
+
+      <!-- Windows 7 -->
+      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
+
+      <!-- Windows 8 -->
+      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
+
+      <!-- Windows 8.1 -->
+      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
+
+      <!-- Windows 10 -->
+      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
+
+    </application>
+  </compatibility>
+
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">
+    <windowsSettings>
+      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
+      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2,permonitor</dpiAwareness>
+    </windowsSettings>
+  </application>
+
+</assembly>

+ 27 - 0
AutoPong/README.md

@@ -0,0 +1,27 @@
+# Auto Pong Sample
+
+![Auto Pong Sample](../Images/AutoPong_1.gif)
+
+This project shows you how to make the classic game of pong, with basic soundfx, in 300 lines of code.
+
+The sample includes:
+
+* Program Architecture: Using public static classes and fields, regions
+* Basic Physics: Velocity, Position, and Collisions
+* Game State Management: Keeping Score, Resetting the game
+* Basic SoundFX and Music: Playing a Reset Jingle, Paddle + Scoring Soundfx, Dynamic Sound Effect Instances
+
+## Player Controls
+
+AutoPong plays itself, so no player input is needed. You can change the code to accept player input, as an exercise.
+
+## Exploring the Sample
+
+* AutoPong doesn't load any content (pngs, wavs, etc...), instead content is created via code.
+* AutoPong is designed using 3 classes: AutoPongGame, AudioSource, and WaveType.
+* The AutoPongGame class is the expected Game class.
+* An AudioSource class is used to make soundfx and music, which utilizes the WaveType for producing different sounds.
+* The simulation and all its controls are built in to the "Update" loop, moving the ball, simulated paddles and win conditions.
+* The draw loop does exactly what you expect and just draws all the game UI including Paddles, the Ball, score and the half-way line (dotted line). All commented for convenience.
+
+If you fancy a challenge, play with the sound generation or even update the paddle controls in the Update loop so you can play yourself (or for the more advanced player, build a proper AI controlled bat)

BIN
Images/AutoPong_1.gif


+ 4 - 4
README.md

@@ -17,11 +17,11 @@ Supported on all platforms | Supported on all platforms |
 The [Platformer 2D](Platformer2D/README.md) sample is a basic 2D platformer pulled from the original XNA samples and upgraded for MonoGame.| [Neon Shooter](NeonShooter/README.md) Is a graphically intensive twin-stick shooter with particle effects and save data from Michael Hoffman |
 |||
 
-| [Coming Soon]() | [Coming Soon]() |
+| [Auto Pong Sample](AutoPong/README.md) | [Coming Soon]() |
 |-|-|
-| Platforms | Platforms |
-| [![MonoGame Sample](Images/MonoGame-Sample.png)]() | [![MonoGame Sample](Images/MonoGame-Sample.png)]() |
-| More samples coming soon | More samples coming soon |
+| Supported on all platforms | Platforms |
+| [![Auto Pong Sample](Images/AutoPong_1.gif)](AutoPong/README.md) | [![MonoGame Sample](Images/MonoGame-Sample.png)]() |
+| A short [sample project](AutoPong/README.md) showing you how to make the classic game of pong, with generated soundfx, in 300 lines of code. | More samples coming soon |
 |||
 
 ## Building the samples