Browse Source

Peer2Peer updated to SDK and MG 3.8.*

CartBlanche 1 week ago
parent
commit
0024fb37ce
47 changed files with 1712 additions and 1320 deletions
  1. 49 0
      Peer2PeerSample/.vscode/launch.json
  2. 158 0
      Peer2PeerSample/.vscode/tasks.json
  3. 0 25
      Peer2PeerSample/Activity1.cs
  4. 15 15
      Peer2PeerSample/Core/Content/Font.spritefont
  5. 0 0
      Peer2PeerSample/Core/Content/Font.xnb
  6. 0 0
      Peer2PeerSample/Core/Content/Game.ico
  7. 0 0
      Peer2PeerSample/Core/Content/PeerToPeer.png
  8. 0 0
      Peer2PeerSample/Core/Content/Tank.tga
  9. 0 0
      Peer2PeerSample/Core/Content/Tank.xnb
  10. 0 0
      Peer2PeerSample/Core/Content/Turret.tga
  11. 0 0
      Peer2PeerSample/Core/Content/Turret.xnb
  12. 0 0
      Peer2PeerSample/Core/Content/gamepad.png
  13. 14 0
      Peer2PeerSample/Core/Peer2PeerSample.Core.csproj
  14. 577 0
      Peer2PeerSample/Core/PeerToPeerGame.cs
  15. 144 152
      Peer2PeerSample/Core/Tank.cs
  16. 0 105
      Peer2PeerSample/Peer2Peer.Android.csproj
  17. 0 93
      Peer2PeerSample/Peer2Peer.iOS.csproj
  18. 16 0
      Peer2PeerSample/Peer2PeerMasterServer/.vscode/launch.json
  19. 34 0
      Peer2PeerSample/Peer2PeerMasterServer/.vscode/tasks.json
  20. 63 0
      Peer2PeerSample/Peer2PeerMasterServer/IDataProvider.cs
  21. 16 0
      Peer2PeerSample/Peer2PeerMasterServer/Peer2PeerMasterServer.csproj
  22. 24 0
      Peer2PeerSample/Peer2PeerMasterServer/Peer2PeerMasterServer.sln
  23. 189 0
      Peer2PeerSample/Peer2PeerMasterServer/Program.cs
  24. 3 0
      Peer2PeerSample/Peer2PeerMasterServer/app.config
  25. 0 68
      Peer2PeerSample/Peer2PeerSample.Linux.csproj
  26. 0 82
      Peer2PeerSample/Peer2PeerSample.MacOS.csproj
  27. 0 85
      Peer2PeerSample/Peer2PeerSample.Windows.csproj
  28. 41 20
      Peer2PeerSample/Peer2PeerSample.sln
  29. 0 572
      Peer2PeerSample/PeerToPeerGame.cs
  30. 30 0
      Peer2PeerSample/Platforms/Android/AndroidManifest.xml
  31. 23 0
      Peer2PeerSample/Platforms/Android/MainActivity.cs
  32. 29 0
      Peer2PeerSample/Platforms/Android/Peer2Peer.Android.csproj
  33. 0 0
      Peer2PeerSample/Platforms/Android/Resources/Drawable/Icon.png
  34. 0 0
      Peer2PeerSample/Platforms/Android/Resources/Drawable/Splash.png
  35. 0 0
      Peer2PeerSample/Platforms/Android/Resources/Resource.Designer.cs
  36. 0 0
      Peer2PeerSample/Platforms/Android/Resources/Values/Styles.xml
  37. 25 0
      Peer2PeerSample/Platforms/Desktop/Peer2PeerSample.DesktopGL.csproj
  38. 14 0
      Peer2PeerSample/Platforms/Desktop/Program.cs
  39. 26 0
      Peer2PeerSample/Platforms/Windows/Peer2PeerSample.Windows.csproj
  40. 22 0
      Peer2PeerSample/Platforms/Windows/Program.cs
  41. 21 0
      Peer2PeerSample/Platforms/iOS/AppDelegate.cs
  42. 0 0
      Peer2PeerSample/Platforms/iOS/Info.plist
  43. 25 0
      Peer2PeerSample/Platforms/iOS/Peer2Peer.iOS.csproj
  44. 14 0
      Peer2PeerSample/Platforms/iOS/Program.cs
  45. 0 97
      Peer2PeerSample/Program.cs
  46. 0 6
      Peer2PeerSample/Properties/AndroidManifest.xml
  47. 140 0
      Peer2PeerSample/README.md

+ 49 - 0
Peer2PeerSample/.vscode/launch.json

@@ -0,0 +1,49 @@
+{
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "name": "Launch Windows",
+            "type": "coreclr",
+            "request": "launch",
+            "preLaunchTask": "build-windows",
+            "program": "${workspaceFolder}/Platforms/Windows/bin/Debug/net8.0-windows/Peer2PeerSample.exe",
+            "args": [],
+            "cwd": "${workspaceFolder}/Platforms/Windows",
+            "console": "internalConsole",
+            "stopAtEntry": false
+        },
+        {
+            "name": "Launch DesktopGL",
+            "type": "coreclr",
+            "request": "launch",
+            "preLaunchTask": "build-desktopgl",
+            "program": "${workspaceFolder}/Platforms/Desktop/bin/Debug/net8.0/Peer2PeerSample.exe",
+            "args": [],
+            "cwd": "${workspaceFolder}/Platforms/Desktop",
+            "console": "internalConsole",
+            "stopAtEntry": false
+        },
+        {
+            "name": "Launch Android",
+            "type": "coreclr",
+            "request": "launch",
+            "preLaunchTask": "build-android",
+            "program": "${workspaceFolder}/Platforms/Android/bin/Debug/net8.0-android/Peer2PeerSample.dll",
+            "args": [],
+            "cwd": "${workspaceFolder}/Platforms/Android",
+            "console": "internalConsole",
+            "stopAtEntry": false
+        },
+        {
+            "name": "Launch iOS",
+            "type": "coreclr",
+            "request": "launch",
+            "preLaunchTask": "build-ios",
+            "program": "${workspaceFolder}/Platforms/iOS/bin/Debug/net8.0-ios/Peer2PeerSample.dll",
+            "args": [],
+            "cwd": "${workspaceFolder}/Platforms/iOS",
+            "console": "internalConsole",
+            "stopAtEntry": false
+        }
+    ]
+}

+ 158 - 0
Peer2PeerSample/.vscode/tasks.json

@@ -0,0 +1,158 @@
+{
+    "version": "2.0.0",
+    "tasks": [
+        {
+            "label": "build-windows",
+            "command": "dotnet",
+            "type": "shell",
+            "args": [
+                "build",
+                "Platforms/Windows/Peer2PeerSample.Windows.csproj",
+                "/property:GenerateFullPaths=true",
+                "/consoleloggerparameters:NoSummary"
+            ],
+            "group": "build",
+            "presentation": {
+                "reveal": "silent"
+            },
+            "problemMatcher": "$msCompile"
+        },
+        {
+            "label": "build-desktopgl",
+            "command": "dotnet",
+            "type": "shell",
+            "args": [
+                "build",
+                "Platforms/Desktop/Peer2PeerSample.DesktopGL.csproj",
+                "/property:GenerateFullPaths=true",
+                "/consoleloggerparameters:NoSummary"
+            ],
+            "group": "build",
+            "presentation": {
+                "reveal": "silent"
+            },
+            "problemMatcher": "$msCompile"
+        },
+        {
+            "label": "build-android",
+            "command": "dotnet",
+            "type": "shell",
+            "args": [
+                "build",
+                "Platforms/Android/Peer2Peer.Android.csproj",
+                "/property:GenerateFullPaths=true",
+                "/consoleloggerparameters:NoSummary"
+            ],
+            "group": "build",
+            "presentation": {
+                "reveal": "silent"
+            },
+            "problemMatcher": "$msCompile"
+        },
+        {
+            "label": "build-ios",
+            "command": "dotnet",
+            "type": "shell",
+            "args": [
+                "build",
+                "Platforms/iOS/Peer2Peer.iOS.csproj",
+                "/property:GenerateFullPaths=true",
+                "/consoleloggerparameters:NoSummary"
+            ],
+            "group": "build",
+            "presentation": {
+                "reveal": "silent"
+            },
+            "problemMatcher": ["$msCompile"]
+        },
+        {
+            "label": "run-windows",
+            "command": "dotnet",
+            "type": "shell",
+            "args": [
+                "run",
+                "--project",
+                "Platforms/Windows/Peer2PeerSample.Windows.csproj"
+            ],
+            "group": {
+                "kind": "test",
+                "isDefault": true
+            },
+            "presentation": {
+                "reveal": "always"
+            },
+            "problemMatcher": "$msCompile"
+        },
+        {
+            "label": "run-desktopgl",
+            "command": "dotnet",
+            "type": "shell",
+            "args": [
+                "run",
+                "--project",
+                "Platforms/Desktop/Peer2PeerSample.DesktopGL.csproj"
+        {
+            "label": "run-android",
+            "command": "dotnet",
+            "type": "shell",
+            "args": [
+                "run",
+                "--project",
+                "Platforms/Android/Peer2Peer.Android.csproj"
+            ],
+            "group": "test",
+            "presentation": {
+                "reveal": "always"
+            },
+            "problemMatcher": ["$msCompile"]
+        },
+        {
+            "label": "run-ios",
+            "command": "dotnet",
+            "type": "shell",
+            "args": [
+                "run",
+                "--project",
+                "Platforms/iOS/Peer2Peer.iOS.csproj"
+            ],
+            "group": "test",
+            "presentation": {
+                "reveal": "always"
+            },
+            "problemMatcher": ["$msCompile"]
+        },
+            ],
+            "group": "test",
+            "presentation": {
+                "reveal": "always"
+            },
+            "problemMatcher": "$msCompile"
+        },
+        {
+            "label": "clean",
+            "command": "dotnet",
+            "type": "shell",
+            "args": [
+                "clean"
+            ],
+            "group": "build",
+            "presentation": {
+                "reveal": "silent"
+            },
+            "problemMatcher": "$msCompile"
+        },
+        {
+            "label": "restore",
+            "command": "dotnet",
+            "type": "shell",
+            "args": [
+                "restore"
+            ],
+            "group": "build",
+            "presentation": {
+                "reveal": "silent"
+            },
+            "problemMatcher": []
+        }
+    ]
+}

+ 0 - 25
Peer2PeerSample/Activity1.cs

@@ -1,25 +0,0 @@
-using Android.App;
-using Android.Content.PM;
-using Android.OS;
-using PeerToPeer;
-
-namespace MonoGame.Samples.PeerToPeerGame.Droid
-{
-    [Activity(Label = "Peer2Peer", MainLauncher = true
-        ,ConfigurationChanges=ConfigChanges.Orientation|ConfigChanges.Keyboard|ConfigChanges.KeyboardHidden
-        , Icon = "@drawable/icon"
-        , Theme = "@style/Theme.Splash"
-        , NoHistory = true)]
-    public class Activity1 : Microsoft.Xna.Framework.AndroidGameActivity
-    {
-        protected override void OnCreate(Bundle bundle)
-        {
-            base.OnCreate(bundle);
-            PeerToPeer.PeerToPeerGame.Activity = this;
-            var g = new PeerToPeer.PeerToPeerGame();
-            SetContentView(g.Window);
-            g.Run();
-        }
-    }
-}
-

+ 15 - 15
Peer2PeerSample/Content/Font.spritefont → Peer2PeerSample/Core/Content/Font.spritefont

@@ -1,16 +1,16 @@
-<?xml version="1.0" encoding="utf-8"?>
-<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
-  <Asset Type="Graphics:FontDescription">
-    <FontName>Segoe UI Mono</FontName>
-    <Size>20</Size>
-    <Spacing>0</Spacing>
-    <UseKerning>true</UseKerning>
-    <Style>Regular</Style>
-    <CharacterRegions>
-      <CharacterRegion>
-        <Start>&#32;</Start>
-        <End>&#126;</End>
-      </CharacterRegion>
-    </CharacterRegions>
-  </Asset>
+<?xml version="1.0" encoding="utf-8"?>
+<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
+  <Asset Type="Graphics:FontDescription">
+    <FontName>Segoe UI Mono</FontName>
+    <Size>20</Size>
+    <Spacing>0</Spacing>
+    <UseKerning>true</UseKerning>
+    <Style>Regular</Style>
+    <CharacterRegions>
+      <CharacterRegion>
+        <Start>&#32;</Start>
+        <End>&#126;</End>
+      </CharacterRegion>
+    </CharacterRegions>
+  </Asset>
 </XnaContent>

+ 0 - 0
Peer2PeerSample/Content/Font.xnb → Peer2PeerSample/Core/Content/Font.xnb


+ 0 - 0
Peer2PeerSample/Game.ico → Peer2PeerSample/Core/Content/Game.ico


+ 0 - 0
Peer2PeerSample/PeerToPeer.png → Peer2PeerSample/Core/Content/PeerToPeer.png


+ 0 - 0
Peer2PeerSample/Content/Tank.tga → Peer2PeerSample/Core/Content/Tank.tga


+ 0 - 0
Peer2PeerSample/Content/Tank.xnb → Peer2PeerSample/Core/Content/Tank.xnb


+ 0 - 0
Peer2PeerSample/Content/Turret.tga → Peer2PeerSample/Core/Content/Turret.tga


+ 0 - 0
Peer2PeerSample/Content/Turret.xnb → Peer2PeerSample/Core/Content/Turret.xnb


+ 0 - 0
Peer2PeerSample/Content/gamepad.png → Peer2PeerSample/Core/Content/gamepad.png


+ 14 - 0
Peer2PeerSample/Core/Peer2PeerSample.Core.csproj

@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <RootNamespace>PeerToPeer</RootNamespace>
+    <AssemblyName>Peer2PeerSample.Core</AssemblyName>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <Nullable>disable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.*" />
+    <ProjectReference Include="..\..\MonoGame.Xna.Framework.Net\MonoGame.Xna.Framework.Net.csproj" />
+  </ItemGroup>
+</Project>

+ 577 - 0
Peer2PeerSample/Core/PeerToPeerGame.cs

@@ -0,0 +1,577 @@
+//-----------------------------------------------------------------------------
+// PeerToPeerGame.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+
+using System;
+
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.GamerServices;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using Microsoft.Xna.Framework.Input.Touch;
+using Microsoft.Xna.Framework.Net;
+
+
+namespace PeerToPeer
+{
+	/// <summary>
+	/// Sample showing how to implement a simple multiplayer
+	/// network session, using a peer-to-peer network topology.
+	/// </summary>
+	public class PeerToPeerGame : Microsoft.Xna.Framework.Game
+	{
+
+		const int screenWidth = 480;
+		const int screenHeight = 540;
+		const int maxGamers = 16;
+		const int maxLocalGamers = 4;
+		GraphicsDeviceManager graphics;
+		SpriteBatch spriteBatch;
+		SpriteFont font;
+		KeyboardState currentKeyboardState;
+		GamePadState currentGamePadState;
+		TouchCollection currentTouchState;
+		NetworkSession networkSession;
+		PacketWriter packetWriter = new PacketWriter();
+		PacketReader packetReader = new PacketReader();
+		string errorMessage;
+		Texture2D gamePadTexture;
+
+
+
+
+		public PeerToPeerGame()
+		{
+			graphics = new GraphicsDeviceManager(this);
+
+			graphics.PreferredBackBufferWidth = screenWidth;
+			graphics.PreferredBackBufferHeight = screenHeight;
+#if MOBILE
+			graphics.IsFullScreen = true;
+#endif
+
+			Content.RootDirectory = "Content";
+
+			Components.Add(new GamerServicesComponent(this));
+		}
+
+
+		/// <summary>
+		/// Load your content.
+		/// </summary>
+		protected override void LoadContent()
+		{
+			spriteBatch = new SpriteBatch(GraphicsDevice);
+
+			font = Content.Load<SpriteFont>("Font");
+
+#if ANDROID || IPHONE
+			gamePadTexture = Content.Load<Texture2D>("gamepad.png");
+			
+			ThumbStickDefinition thumbStickLeft = new ThumbStickDefinition();
+			thumbStickLeft.Position = new Vector2(10,400);
+			thumbStickLeft.Texture = gamePadTexture;
+			thumbStickLeft.TextureRect = new Rectangle(2,2,68,68);
+			
+			GamePad.LeftThumbStickDefinition = thumbStickLeft;
+			
+			ThumbStickDefinition thumbStickRight = new ThumbStickDefinition();
+			thumbStickRight.Position = new Vector2(240,400);
+			thumbStickRight.Texture = gamePadTexture;
+			thumbStickRight.TextureRect = new Rectangle(2,2,68,68);
+			
+			GamePad.RightThumbStickDefinition = thumbStickRight;
+#endif
+		}
+
+
+
+
+
+		/// <summary>
+		/// Allows the game to run logic.
+		/// </summary>
+		protected override void Update(GameTime gameTime)
+		{
+			HandleInput();
+
+			if (networkSession == null)
+			{
+				// If we are not in a network session, update the
+				// menu screen that will let us create or join one.
+				UpdateMenuScreen();
+			}
+			else
+			{
+				// If we are in a network session, update it.
+				UpdateNetworkSession();
+			}
+
+			base.Update(gameTime);
+		}
+
+
+		/// <summary>
+		/// Menu screen provides options to create or join network sessions.
+		/// </summary>
+		void UpdateMenuScreen()
+		{
+			if (IsActive)
+			{
+				if (Gamer.SignedInGamers.Count == 0)
+				{
+					// If there are no profiles signed in, we cannot proceed.
+					// Show the Guide so the user can sign in.
+					Guide.ShowSignIn(maxLocalGamers, false);
+				}
+				else if (IsPressed(Keys.A, Buttons.A))
+				{
+					// Create a new session?
+					CreateSession();
+				}
+				else if (IsPressed(Keys.X, Buttons.X))
+				{
+					CreateLiveSession();
+				}
+				else if (IsPressed(Keys.Y, Buttons.Y))
+				{
+					JoinSession(NetworkSessionType.PlayerMatch);
+				}
+				else if (IsPressed(Keys.B, Buttons.B))
+				{
+					// Join an existing session?
+					JoinSession(NetworkSessionType.SystemLink);
+				}
+			}
+		}
+
+		private void JoinLiveSession()
+		{
+			throw new NotImplementedException();
+		}
+
+		private void CreateLiveSession()
+		{
+			DrawMessage("Creating Live session...");
+
+			try
+			{
+				networkSession = NetworkSession.Create(NetworkSessionType.PlayerMatch,
+							maxLocalGamers, maxGamers);
+
+				HookSessionEvents();
+				//networkSession.AddLocalGamer();
+			}
+			catch (Exception e)
+			{
+				errorMessage = e.Message;
+			}
+		}
+
+
+		/// <summary>
+		/// Starts hosting a new network session.
+		/// </summary>
+		void CreateSession()
+		{
+			DrawMessage("Creating session...");
+
+			try
+			{
+				networkSession = NetworkSession.Create(NetworkSessionType.SystemLink, maxLocalGamers, maxGamers);
+
+				HookSessionEvents();
+				
+				//networkSession.AddLocalGamer();
+			}
+			catch (Exception e)
+			{
+				errorMessage = e.Message;
+			}
+		}
+
+
+		/// <summary>
+		/// Joins an existing network session.
+		/// </summary>
+		void JoinSession(NetworkSessionType type)
+		{
+			DrawMessage("Joining session...");
+
+			try
+			{
+				// Search for sessions.
+				using (AvailableNetworkSessionCollection availableSessions =
+				NetworkSession.Find(type,
+						maxLocalGamers, null))
+				{
+					if (availableSessions.Count == 0)
+					{
+						errorMessage = "No network sessions found.";
+						return;
+					}
+
+					// Join the first session we found.
+					networkSession = NetworkSession.Join(availableSessions[0]);
+
+					HookSessionEvents();
+				}
+			}
+			catch (Exception e)
+			{
+				errorMessage = e.Message;
+			}
+		}
+
+
+		/// <summary>
+		/// After creating or joining a network session, we must subscribe to
+		/// some events so we will be notified when the session changes state.
+		/// </summary>
+		void HookSessionEvents()
+		{
+			networkSession.GamerJoined += GamerJoinedEventHandler;
+			networkSession.SessionEnded += SessionEndedEventHandler;
+		}
+
+
+		/// <summary>
+		/// This event handler will be called whenever a new gamer joins the session.
+		/// We use it to allocate a Tank object, and associate it with the new gamer.
+		/// </summary>
+		void GamerJoinedEventHandler(object sender, GamerJoinedEventArgs e)
+		{
+			int gamerIndex = networkSession.AllGamers.IndexOf(e.Gamer);
+
+			e.Gamer.Tag = new Tank(gamerIndex, Content, screenWidth, screenHeight);
+		}
+
+
+		/// <summary>
+		/// Event handler notifies us when the network session has ended.
+		/// </summary>
+		void SessionEndedEventHandler(object sender, NetworkSessionEndedEventArgs e)
+		{
+			errorMessage = e.EndReason.ToString();
+
+			networkSession.Dispose();
+			networkSession = null;
+		}
+
+
+		/// <summary>
+		/// Updates the state of the network session, moving the tanks
+		/// around and synchronizing their state over the network.
+		/// </summary>
+		void UpdateNetworkSession()
+		{
+			// Update our locally controlled tanks, and send their
+			// latest position data to everyone in the session.
+			foreach (LocalNetworkGamer gamer in networkSession.LocalGamers)
+			{
+				UpdateLocalGamer(gamer);
+			}
+
+			// Pump the underlying session object.
+			networkSession.Update();
+
+			// Make sure the session has not ended.
+			if (networkSession == null)
+				return;
+
+			// Read any packets telling us the positions of remotely controlled tanks.
+			foreach (LocalNetworkGamer gamer in networkSession.LocalGamers)
+			{
+				ReadIncomingPackets(gamer);
+			}
+		}
+
+
+		/// <summary>
+		/// Helper for updating a locally controlled gamer.
+		/// </summary>
+		void UpdateLocalGamer(LocalNetworkGamer gamer)
+		{
+			// Look up what tank is associated with this local player.
+			Tank localTank = gamer.Tag as Tank;
+
+			if (localTank != null)
+			{
+
+				// Update the tank.
+				ReadTankInputs(localTank, (PlayerIndex)gamer.SignedInGamer.PlayerIndex);
+
+				localTank.Update();
+
+				// Write the tank state into a network packet.
+				packetWriter.Write(localTank.Position);
+				packetWriter.Write(localTank.TankRotation);
+				packetWriter.Write(localTank.TurretRotation);
+
+				// Send the data to everyone in the session.
+				gamer.SendData(packetWriter, SendDataOptions.InOrder);
+			}
+		}
+
+
+		/// <summary>
+		/// Helper for reading incoming network packets.
+		/// </summary>
+		void ReadIncomingPackets(LocalNetworkGamer gamer)
+		{
+			// Keep reading as long as incoming packets are available.
+			while (gamer.IsDataAvailable)
+			{
+				NetworkGamer sender;
+
+				// Read a single packet from the network.
+				gamer.ReceiveData(packetReader, out sender);
+
+				// Discard packets sent by local gamers: we already know their state!
+				if (sender.IsLocal)
+					continue;
+
+				// Look up the tank associated with whoever sent this packet.
+				Tank remoteTank = sender.Tag as Tank;
+				if (remoteTank != null)
+				{
+
+					// Read the state of this tank from the network packet.
+					remoteTank.Position = packetReader.ReadVector2();
+					remoteTank.TankRotation = packetReader.ReadSingle();
+					remoteTank.TurretRotation = packetReader.ReadSingle();
+
+				}
+			}
+		}
+
+
+
+
+
+		/// <summary>
+		/// This is called when the game should draw itself.
+		/// </summary>
+		protected override void Draw(GameTime gameTime)
+		{
+			GraphicsDevice.Clear(Color.CornflowerBlue);
+
+			if (networkSession == null)
+			{
+				// If we are not in a network session, draw the
+				// menu screen that will let us create or join one.
+				DrawMenuScreen();
+			}
+			else
+			{
+				// If we are in a network session, draw it.
+				DrawNetworkSession(gameTime);
+			}
+
+			base.Draw(gameTime);
+		}
+
+
+		/// <summary>
+		/// Draws the startup screen used to create and join network sessions.
+		/// </summary>
+		void DrawMenuScreen()
+		{
+			string message = string.Empty;
+
+			if (!string.IsNullOrEmpty(errorMessage))
+				message += "Error:\n" + errorMessage.Replace(". ", ".\n") + "\n\n";
+
+			message += "A = create LAN session\n" +
+			"B = join LAN session" + "\n" +
+			"X = create live session\n" +
+			"Y = join live session\n";
+
+			spriteBatch.Begin();
+
+			spriteBatch.DrawString(font, message, new Vector2(61, 161), Color.Black);
+			spriteBatch.DrawString(font, message, new Vector2(60, 160), Color.White);
+
+			spriteBatch.End();
+		}
+
+
+		/// <summary>
+		/// Draws the state of an active network session.
+		/// </summary>
+		void DrawNetworkSession(GameTime gameTime)
+		{
+			spriteBatch.Begin();
+
+			// For each person in the session...
+			foreach (NetworkGamer gamer in networkSession.AllGamers)
+			{
+				// Look up the tank object belonging to this network gamer.
+				Tank tank = gamer.Tag as Tank;
+
+				if (tank != null)
+				{
+					// Draw the tank.
+					tank.Draw(spriteBatch);
+
+					// Draw a gamertag label.
+					string label = gamer.Gamertag;
+					Color labelColor = Color.Black;
+					Vector2 labelOffset = new Vector2(100, 150);
+
+					if (gamer.IsHost)
+						label += " (host)";
+
+					// Flash the gamertag to yellow when the player is talking.
+					if (gamer.IsTalking)
+						labelColor = Color.Yellow;
+
+					spriteBatch.DrawString(font, label, tank.Position, labelColor, 0,
+						labelOffset, 0.6f, SpriteEffects.None, 0);
+				}
+			}
+
+#if ANDROID || IPHONE
+			GamePad.Draw(gameTime, spriteBatch);
+#endif
+
+			spriteBatch.End();
+		}
+
+
+		/// <summary>
+		/// Helper draws notification messages before calling blocking network methods.
+		/// </summary>
+		void DrawMessage(string message)
+		{
+			if (!BeginDraw())
+				return;
+
+			GraphicsDevice.Clear(Color.CornflowerBlue);
+
+			spriteBatch.Begin();
+
+			spriteBatch.DrawString(font, message, new Vector2(161, 161), Color.Black);
+			spriteBatch.DrawString(font, message, new Vector2(160, 160), Color.White);
+
+			spriteBatch.End();
+
+			EndDraw();
+		}
+
+
+
+
+
+		/// <summary>
+		/// Handles input.
+		/// </summary>
+		private void HandleInput()
+		{
+			currentKeyboardState = Keyboard.GetState();
+			currentGamePadState = GamePad.GetState(PlayerIndex.One);
+			currentTouchState = TouchPanel.GetState();
+
+			// Check for exit.
+			if (IsActive && IsPressed(Keys.Escape, Buttons.Back))
+			{
+				Exit();
+			}
+
+			// Only test of Menu touches when networkSession is null
+			if (networkSession == null)
+			{
+				// Doing very very basic touch detection for menu
+				if (currentTouchState.Count > 0)
+				{
+					Console.WriteLine(string.Format("X:{0}, Y:{1}", currentTouchState[0].Position.X, currentTouchState[0].Position.Y));
+					if ((currentTouchState[0].Position.X > 60) && (currentTouchState[0].Position.Y > 160)
+					&& (currentTouchState[0].Position.X < 220) && (currentTouchState[0].Position.Y < 190))
+					{
+						CreateSession();
+					}
+
+					if ((currentTouchState[0].Position.X > 60) && (currentTouchState[0].Position.Y > 200)
+					&& (currentTouchState[0].Position.X < 220) && (currentTouchState[0].Position.Y < 230))
+					{
+						CreateLiveSession();
+					}
+
+					if ((currentTouchState[0].Position.X > 60) && (currentTouchState[0].Position.Y > 240)
+					&& (currentTouchState[0].Position.X < 220) && (currentTouchState[0].Position.Y < 270))
+					{
+						JoinSession(NetworkSessionType.PlayerMatch);
+					}
+
+					if ((currentTouchState[0].Position.X > 60) && (currentTouchState[0].Position.Y > 280)
+					&& (currentTouchState[0].Position.X < 220) && (currentTouchState[0].Position.Y < 310))
+					{
+						JoinSession(NetworkSessionType.SystemLink);
+					}
+				}
+			}
+
+		}
+
+
+		/// <summary>
+		/// Checks if the specified button is pressed on either keyboard or gamepad.
+		/// </summary>
+		bool IsPressed(Keys key, Buttons button)
+		{
+			return (currentKeyboardState.IsKeyDown(key) ||
+			currentGamePadState.IsButtonDown(button));
+		}
+
+
+		/// <summary>
+		/// Reads input data from keyboard and gamepad, and stores
+		/// it into the specified tank object.
+		/// </summary>
+		void ReadTankInputs(Tank tank, PlayerIndex playerIndex)
+		{
+			// Read the gamepad.
+			GamePadState gamePad = GamePad.GetState(playerIndex);
+
+			Vector2 tankInput = gamePad.ThumbSticks.Left;
+			Vector2 turretInput = gamePad.ThumbSticks.Right;
+
+			// Read the keyboard.
+			KeyboardState keyboard = Keyboard.GetState();
+
+			if (keyboard.IsKeyDown(Keys.Left))
+				tankInput.X = -1;
+			else if (keyboard.IsKeyDown(Keys.Right))
+				tankInput.X = 1;
+
+			if (keyboard.IsKeyDown(Keys.Up))
+				tankInput.Y = 1;
+			else if (keyboard.IsKeyDown(Keys.Down))
+				tankInput.Y = -1;
+
+			if (keyboard.IsKeyDown(Keys.A))
+				turretInput.X = -1;
+			else if (keyboard.IsKeyDown(Keys.D))
+				turretInput.X = 1;
+
+			if (keyboard.IsKeyDown(Keys.W))
+				turretInput.Y = 1;
+			else if (keyboard.IsKeyDown(Keys.S))
+				turretInput.Y = -1;
+
+			// Normalize the input vectors.
+			if (tankInput.Length() > 1)
+				tankInput.Normalize();
+
+			if (turretInput.Length() > 1)
+				turretInput.Normalize();
+
+			// Store these input values into the tank object.
+			tank.TankInput = tankInput;
+			tank.TurretInput = turretInput;
+		}
+	}
+}

+ 144 - 152
Peer2PeerSample/Tank.cs → Peer2PeerSample/Core/Tank.cs

@@ -1,152 +1,144 @@
-#region File Description
-//-----------------------------------------------------------------------------
-// Tank.cs
-//
-// Microsoft XNA Community Game Platform
-// Copyright (C) Microsoft Corporation. All rights reserved.
-//-----------------------------------------------------------------------------
-#endregion
-
-#region Using Statements
-using System;
-using Microsoft.Xna.Framework;
-using Microsoft.Xna.Framework.Content;
-using Microsoft.Xna.Framework.Graphics;
-using Microsoft.Xna.Framework.Input;
-
-#endregion
-
-namespace PeerToPeer
-{
-	/// <summary>
-	/// Each player controls a tank, which they can drive around the screen.
-	/// This class implements the logic for moving and drawing the tank, and
-	/// responds to input that is passed in from outside. The Tank class does
-	/// not implement any networking functionality, however: that is all
-	/// handled by the main game class.
-	/// </summary>
-	class Tank
-	{
-	#region Constants
-
-		// Constants control how fast the tank moves and turns.
-		const float TankTurnRate = 0.01f;
-		const float TurretTurnRate = 0.03f;
-		const float TankSpeed = 0.3f;
-		const float TankFriction = 0.9f;
-
-	#endregion
-
-	#region Fields
-
-		// The current position and rotation of the tank.
-		public Vector2 Position;
-		public Vector2 Velocity;
-		public float TankRotation;
-		public float TurretRotation;
-
-		// Input controls can be read from keyboard, gamepad, or the network.
-		public Vector2 TankInput;
-		public Vector2 TurretInput;
-
-		// Textures used to draw the tank.
-		Texture2D tankTexture;
-		Texture2D turretTexture;
-		Vector2 screenSize;
-
-	#endregion
-
-
-		/// <summary>
-		/// Constructs a new Tank instance.
-		/// </summary>
-		public Tank (int gamerIndex,ContentManager content, 
-			int screenWidth,int screenHeight)
-			{
-			// Use the gamer index to compute a starting position, so each player
-			// starts in a different place as opposed to all on top of each other.
-			Position.X = screenWidth / 4 + (gamerIndex % 5) * screenWidth / 8;
-			Position.Y = screenHeight / 4 + (gamerIndex / 5) * screenHeight / 5;
-
-			TankRotation = -MathHelper.PiOver2;
-			TurretRotation = -MathHelper.PiOver2;
-
-			tankTexture = content.Load<Texture2D> ("Tank");
-			turretTexture = content.Load<Texture2D> ("Turret");
-
-			screenSize = new Vector2 (screenWidth, screenHeight);
-		}
-
-
-		/// <summary>
-		/// Moves the tank in response to the current input settings.
-		/// </summary>
-		public void Update ()
-		{
-			// Gradually turn the tank and turret to face the requested direction.
-			TankRotation = TurnToFace (TankRotation, TankInput, TankTurnRate);
-			TurretRotation = TurnToFace (TurretRotation, TurretInput, TurretTurnRate);
-
-			// How close the desired direction is the tank facing?
-			Vector2 tankForward = new Vector2 ((float)Math.Cos (TankRotation),
-						(float)Math.Sin (TankRotation));
-
-			Vector2 targetForward = new Vector2 (TankInput.X, -TankInput.Y);
-
-			float facingForward = Vector2.Dot (tankForward, targetForward);
-
-			// If we have finished turning, also start moving forward.
-			if (facingForward > 0)
-				Velocity += tankForward * facingForward * facingForward * TankSpeed;
-
-			// Update the position and velocity.
-			Position += Velocity;
-			Velocity *= TankFriction;
-
-			// Clamp so the tank cannot drive off the edge of the screen.
-			Position = Vector2.Clamp (Position, Vector2.Zero, screenSize);
-		}
-
-
-		/// <summary>
-		/// Gradually rotates the tank to face the specified direction.
-		/// </summary>
-		static float TurnToFace (float rotation, Vector2 target, float turnRate)
-		{
-			if (target == Vector2.Zero)
-				return rotation;
-
-			float angle = (float)Math.Atan2 (-target.Y, target.X);
-
-			float difference = rotation - angle;
-
-			while (difference > MathHelper.Pi)
-				difference -= MathHelper.TwoPi;
-
-			while (difference < -MathHelper.Pi)
-				difference += MathHelper.TwoPi;
-
-			turnRate *= Math.Abs (difference);
-
-			if (difference < 0)
-				return rotation + Math.Min (turnRate, -difference);
-			else
-				return rotation - Math.Min (turnRate, difference);
-		}
-
-
-		/// <summary>
-		/// Draws the tank and turret.
-		/// </summary>
-		public void Draw (SpriteBatch spriteBatch)
-		{
-			Vector2 origin = new Vector2 (tankTexture.Width / 2, tankTexture.Height / 2);
-
-			spriteBatch.Draw (tankTexture, Position, null, Color.White, 
-				TankRotation, origin, 1, SpriteEffects.None, 0);
-
-			spriteBatch.Draw (turretTexture, Position, null, Color.White, 
-				TurretRotation, origin, 1, SpriteEffects.None, 0);
-		}
-	}
-}
+//-----------------------------------------------------------------------------
+// Tank.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+
+
+namespace PeerToPeer
+{
+	/// <summary>
+	/// Each player controls a tank, which they can drive around the screen.
+	/// This class implements the logic for moving and drawing the tank, and
+	/// responds to input that is passed in from outside. The Tank class does
+	/// not implement any networking functionality, however: that is all
+	/// handled by the main game class.
+	/// </summary>
+	class Tank
+	{
+
+		// Constants control how fast the tank moves and turns.
+		const float TankTurnRate = 0.01f;
+		const float TurretTurnRate = 0.03f;
+		const float TankSpeed = 0.3f;
+		const float TankFriction = 0.9f;
+
+
+
+		// The current position and rotation of the tank.
+		public Vector2 Position;
+		public Vector2 Velocity;
+		public float TankRotation;
+		public float TurretRotation;
+
+		// Input controls can be read from keyboard, gamepad, or the network.
+		public Vector2 TankInput;
+		public Vector2 TurretInput;
+
+		// Textures used to draw the tank.
+		Texture2D tankTexture;
+		Texture2D turretTexture;
+		Vector2 screenSize;
+
+
+
+		/// <summary>
+		/// Constructs a new Tank instance.
+		/// </summary>
+		public Tank(int gamerIndex, ContentManager content,
+			int screenWidth, int screenHeight)
+		{
+			// Use the gamer index to compute a starting position, so each player
+			// starts in a different place as opposed to all on top of each other.
+			Position.X = screenWidth / 4 + (gamerIndex % 5) * screenWidth / 8;
+			Position.Y = screenHeight / 4 + (gamerIndex / 5) * screenHeight / 5;
+
+			TankRotation = -MathHelper.PiOver2;
+			TurretRotation = -MathHelper.PiOver2;
+
+			tankTexture = content.Load<Texture2D>("Tank");
+			turretTexture = content.Load<Texture2D>("Turret");
+
+			screenSize = new Vector2(screenWidth, screenHeight);
+		}
+
+
+		/// <summary>
+		/// Moves the tank in response to the current input settings.
+		/// </summary>
+		public void Update()
+		{
+			// Gradually turn the tank and turret to face the requested direction.
+			TankRotation = TurnToFace(TankRotation, TankInput, TankTurnRate);
+			TurretRotation = TurnToFace(TurretRotation, TurretInput, TurretTurnRate);
+
+			// How close the desired direction is the tank facing?
+			Vector2 tankForward = new Vector2((float)Math.Cos(TankRotation),
+						(float)Math.Sin(TankRotation));
+
+			Vector2 targetForward = new Vector2(TankInput.X, -TankInput.Y);
+
+			float facingForward = Vector2.Dot(tankForward, targetForward);
+
+			// If we have finished turning, also start moving forward.
+			if (facingForward > 0)
+				Velocity += tankForward * facingForward * facingForward * TankSpeed;
+
+			// Update the position and velocity.
+			Position += Velocity;
+			Velocity *= TankFriction;
+
+			// Clamp so the tank cannot drive off the edge of the screen.
+			Position = Vector2.Clamp(Position, Vector2.Zero, screenSize);
+		}
+
+
+		/// <summary>
+		/// Gradually rotates the tank to face the specified direction.
+		/// </summary>
+		static float TurnToFace(float rotation, Vector2 target, float turnRate)
+		{
+			if (target == Vector2.Zero)
+				return rotation;
+
+			float angle = (float)Math.Atan2(-target.Y, target.X);
+
+			float difference = rotation - angle;
+
+			while (difference > MathHelper.Pi)
+				difference -= MathHelper.TwoPi;
+
+			while (difference < -MathHelper.Pi)
+				difference += MathHelper.TwoPi;
+
+			turnRate *= Math.Abs(difference);
+
+			if (difference < 0)
+				return rotation + Math.Min(turnRate, -difference);
+			else
+				return rotation - Math.Min(turnRate, difference);
+		}
+
+
+		/// <summary>
+		/// Draws the tank and turret.
+		/// </summary>
+		public void Draw(SpriteBatch spriteBatch)
+		{
+			Vector2 origin = new Vector2(tankTexture.Width / 2, tankTexture.Height / 2);
+
+			spriteBatch.Draw(tankTexture, Position, null, Color.White,
+				TankRotation, origin, 1, SpriteEffects.None, 0);
+
+			spriteBatch.Draw(turretTexture, Position, null, Color.White,
+				TurretRotation, origin, 1, SpriteEffects.None, 0);
+		}
+	}
+}

+ 0 - 105
Peer2PeerSample/Peer2Peer.Android.csproj

@@ -1,105 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <PropertyGroup>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
-    <ProductVersion>8.0.30703</ProductVersion>
-    <SchemaVersion>2.0</SchemaVersion>
-    <ProjectGuid>{56F6C06F-6F07-402E-89A5-39EDB037A516}</ProjectGuid>
-    <ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
-    <OutputType>Library</OutputType>
-    <AppDesignerFolder>Properties</AppDesignerFolder>
-    <RootNamespace>MonoGame.Samples.Peer2Peer.Android</RootNamespace>
-    <AssemblyName>MonoGame.Samples.Peer2Peer.Android</AssemblyName>
-    <FileAlignment>512</FileAlignment>
-    <AndroidApplication>true</AndroidApplication>
-    <AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
-    <AndroidSupportedAbis>armeabi</AndroidSupportedAbis>
-    <AndroidStoreUncompressedFileExtensions />
-    <MandroidI18n />
-    <AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-    <DebugSymbols>True</DebugSymbols>
-    <DebugType>full</DebugType>
-    <Optimize>False</Optimize>
-    <OutputPath>bin\Debug\</OutputPath>
-    <DefineConstants>TRACE;DEBUG;ANDROID</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <AndroidLinkMode>None</AndroidLinkMode>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-    <DebugType>pdbonly</DebugType>
-    <Optimize>True</Optimize>
-    <OutputPath>bin\Release\</OutputPath>
-    <DefineConstants>TRACE;ANDROID</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <AndroidUseSharedRuntime>False</AndroidUseSharedRuntime>
-    <AndroidLinkMode>SdkOnly</AndroidLinkMode>
-  </PropertyGroup>
-  <ItemGroup>
-    <Reference Include="Mono.Android" />
-    <Reference Include="mscorlib" />
-    <Reference Include="OpenTK" />
-    <Reference Include="System" />
-    <Reference Include="System.Core" />
-    <Reference Include="System.Xml.Linq" />
-    <Reference Include="System.Xml" />
-  </ItemGroup>
-  <ItemGroup>
-    <Compile Include="Activity1.cs" />
-    <Compile Include="Resources\Resource.Designer.cs" />
-    <Compile Include="PeerToPeerGame.cs">
-      <Link>PeerToPeerGame.cs</Link>
-    </Compile>
-    <Compile Include="Tank.cs">
-      <Link>Tank.cs</Link>
-    </Compile>
-  </ItemGroup>
-  <Import Project="$(MSBuildExtensionsPath)\Novell\Novell.MonoDroid.CSharp.targets" />
-  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
-     Other similar extension points exist, see Microsoft.Common.targets.
-  <Target Name="BeforeBuild">
-  </Target>
-  <Target Name="AfterBuild">
-  </Target>
-  -->
-  <ItemGroup>
-    <AndroidAsset Include="Content\Font.xnb">
-      <Link>Assets\Content\Font.xnb</Link>
-    </AndroidAsset>
-    <AndroidAsset Include="Content\gamepad.png">
-      <Link>Assets\Content\gamepad.png</Link>
-    </AndroidAsset>
-    <AndroidAsset Include="Content\Tank.xnb">
-      <Link>Assets\Content\Tank.xnb</Link>
-    </AndroidAsset>
-    <AndroidAsset Include="Content\Turret.xnb">
-      <Link>Assets\Content\Turret.xnb</Link>
-    </AndroidAsset>
-  </ItemGroup>
-  <ItemGroup>
-    <None Include="Properties\AndroidManifest.xml" />
-  </ItemGroup>
-  <ItemGroup>
-    <AndroidResource Include="Resources\Drawable\Icon.png" />
-  </ItemGroup>
-  <ItemGroup>
-    <AndroidResource Include="Resources\Drawable\Splash.png" />
-  </ItemGroup>
-  <ItemGroup>
-    <AndroidResource Include="Resources\Values\Styles.xml" />
-  </ItemGroup>
-  <ItemGroup>
-    <ProjectReference Include="..\..\ThirdParty\Lidgren.Network\Lidgren.Network.Android.csproj">
-      <Project>{565129E0-4EE5-4F6F-B403-C3484C9740BE}</Project>
-      <Name>Lidgren.Network.Android</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\MonoGame.Framework\MonoGame.Framework.Android.csproj">
-      <Project>{BA9476CF-99BA-4D03-92F2-73D2C5E58883}</Project>
-      <Name>MonoGame.Framework.Android</Name>
-    </ProjectReference>
-  </ItemGroup>
-</Project>

+ 0 - 93
Peer2PeerSample/Peer2Peer.iOS.csproj

@@ -1,93 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <PropertyGroup>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">iPhoneSimulator</Platform>
-    <ProductVersion>10.0.0</ProductVersion>
-    <SchemaVersion>2.0</SchemaVersion>
-    <ProjectGuid>{1217FD6D-AFF5-4A21-AA0C-0AE7F14B848F}</ProjectGuid>
-    <ProjectTypeGuids>{6BC8ED88-2882-458C-8E55-DFD12B67127B};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
-    <OutputType>Exe</OutputType>
-    <RootNamespace>MonoGame.Samples.Peer2Peer</RootNamespace>
-    <AssemblyName>MonoGameSamplesPeer2Peer</AssemblyName>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhoneSimulator' ">
-    <DebugSymbols>True</DebugSymbols>
-    <DebugType>full</DebugType>
-    <Optimize>False</Optimize>
-    <OutputPath>bin\iPhoneSimulator\Debug</OutputPath>
-    <DefineConstants>DEBUG;IPHONE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <MtouchLink>None</MtouchLink>
-    <MtouchDebug>True</MtouchDebug>
-    <ConsolePause>False</ConsolePause>
-    <MtouchI18n />
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhoneSimulator' ">
-    <DebugType>none</DebugType>
-    <Optimize>False</Optimize>
-    <OutputPath>bin\iPhoneSimulator\Release</OutputPath>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <MtouchLink>None</MtouchLink>
-    <ConsolePause>False</ConsolePause>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhone' ">
-    <DebugSymbols>True</DebugSymbols>
-    <DebugType>full</DebugType>
-    <Optimize>False</Optimize>
-    <OutputPath>bin\iPhone\Debug</OutputPath>
-    <DefineConstants>DEBUG</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <MtouchDebug>True</MtouchDebug>
-    <CodesignKey>iPhone Developer</CodesignKey>
-    <ConsolePause>False</ConsolePause>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhone' ">
-    <DebugType>none</DebugType>
-    <Optimize>False</Optimize>
-    <OutputPath>bin\iPhone\Release</OutputPath>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <ConsolePause>False</ConsolePause>
-    <CodesignKey>iPhone Developer</CodesignKey>
-  </PropertyGroup>
-  <ItemGroup>
-    <Reference Include="System" />
-    <Reference Include="System.Xml" />
-    <Reference Include="System.Core" />
-    <Reference Include="monotouch" />
-    <Reference Include="OpenTK" />
-  </ItemGroup>
-  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
-  <ItemGroup>
-    <None Include="Info.iOS.plist">
-      <Link>Info.plist</Link>
-    </None>
-  </ItemGroup>
-  <ItemGroup>
-    <Compile Include="Program.cs" />
-    <Compile Include="PeerToPeerGame.cs">
-      <Link>PeerToPeerGame.cs</Link>
-    </Compile>
-    <Compile Include="Tank.cs">
-      <Link>Tank.cs</Link>
-    </Compile>
-  </ItemGroup>
-  <ItemGroup>
-    <Content Include="Content\gamepad.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="Content\Tank.xnb">
-      <Link>Content\Tank.xnb</Link>
-    </Content>
-    <Content Include="Content\Font.xnb">
-      <Link>Content\Font.xnb</Link>
-    </Content>
-    <Content Include="Content\Turret.xnb">
-      <Link>Content\Turret.xnb</Link>
-    </Content>
-  </ItemGroup>
-</Project>

+ 16 - 0
Peer2PeerSample/Peer2PeerMasterServer/.vscode/launch.json

@@ -0,0 +1,16 @@
+{
+  "version": "0.2.0",
+  "configurations": [
+    {
+      "name": "Launch Peer2PeerMasterServer",
+      "type": "coreclr",
+      "request": "launch",
+      "preLaunchTask": "build",
+      "program": "${workspaceFolder}/bin/Debug/net8.0/Peer2PeerMasterServer.exe",
+      "args": [],
+      "cwd": "${workspaceFolder}",
+      "stopAtEntry": false,
+      "console": "internalConsole"
+    }
+  ]
+}

+ 34 - 0
Peer2PeerSample/Peer2PeerMasterServer/.vscode/tasks.json

@@ -0,0 +1,34 @@
+{
+  "version": "2.0.0",
+  "tasks": [
+    {
+      "label": "build",
+      "command": "dotnet",
+      "type": "shell",
+      "args": [
+        "build",
+        "Peer2PeerMasterServer.csproj"
+      ],
+      "group": {
+        "kind": "build",
+        "isDefault": true
+      },
+      "problemMatcher": "$msCompile"
+    },
+    {
+      "label": "run",
+      "command": "dotnet",
+      "type": "shell",
+      "args": [
+        "run",
+        "--project",
+        "Peer2PeerMasterServer.csproj"
+      ],
+      "group": {
+        "kind": "test",
+        "isDefault": false
+      },
+      "problemMatcher": "$msCompile"
+    }
+  ]
+}

+ 63 - 0
Peer2PeerSample/Peer2PeerMasterServer/IDataProvider.cs

@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Peer2PeerMasterServer
+{
+    interface IDataProvider
+    {
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        T Get<T>(object id) where T : class;
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="expression"></param>
+        /// <returns></returns>
+        T Get<T>(System.Linq.Expressions.Expression<Func<T, bool>> expression) where T : class;
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <returns></returns>
+        IQueryable<T> Query<T>() where T : class;
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <returns></returns>
+        T Update<T>() where T : class;
+       
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <returns></returns>
+        bool Delete<T>() where T : class;
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="entities"></param>
+        /// <returns></returns>
+        bool Delete<T>(IList<T> entities) where T : class;
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="item"></param>
+        /// <returns></returns>
+        T Add<T>(T item) where T : class;
+    }
+}

+ 16 - 0
Peer2PeerSample/Peer2PeerMasterServer/Peer2PeerMasterServer.csproj

@@ -0,0 +1,16 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net8.0</TargetFramework>
+    <RootNamespace>Peer2PeerMasterServer</RootNamespace>
+    <AssemblyName>Peer2PeerMasterServer</AssemblyName>
+    <Company>Microsoft</Company>
+    <Product>Peer2PeerMasterServer</Product>
+    <Copyright>Copyright © Microsoft 2011</Copyright>
+    <AssemblyVersion>1.0.0.0</AssemblyVersion>
+    <FileVersion>1.0.0.0</FileVersion>
+  </PropertyGroup>
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.*" />
+  </ItemGroup>
+</Project>

+ 24 - 0
Peer2PeerSample/Peer2PeerMasterServer/Peer2PeerMasterServer.sln

@@ -0,0 +1,24 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.2.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Peer2PeerMasterServer", "Peer2PeerMasterServer.csproj", "{DCA7F813-4F7C-6881-87B0-CA78B8E60C3F}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{DCA7F813-4F7C-6881-87B0-CA78B8E60C3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{DCA7F813-4F7C-6881-87B0-CA78B8E60C3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{DCA7F813-4F7C-6881-87B0-CA78B8E60C3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{DCA7F813-4F7C-6881-87B0-CA78B8E60C3F}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {6624143B-39E3-432E-8284-3C8FF342E1D5}
+	EndGlobalSection
+EndGlobal

+ 189 - 0
Peer2PeerSample/Peer2PeerMasterServer/Program.cs

@@ -0,0 +1,189 @@
+
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Peer2PeerMasterServer
+{
+    class Program
+    {
+        static async Task Main(string[] args)
+        {
+            Console.WriteLine("Server Started");
+            var registeredHosts = new Dictionary<IPEndPoint, AvailableGame>();
+            int port = 6000;
+            using var udp = new UdpClient(port);
+            var cts = new CancellationTokenSource();
+
+            Console.WriteLine($"Listening on UDP port {port}");
+            Console.WriteLine("Press Ctrl+C to quit");
+
+            Console.CancelKeyPress += (s, e) => {
+                e.Cancel = true;
+                cts.Cancel();
+            };
+
+            try
+            {
+                while (!cts.Token.IsCancellationRequested)
+                {
+                    var receiveTask = udp.ReceiveAsync();
+                    var completedTask = await Task.WhenAny(receiveTask, Task.Delay(10, cts.Token));
+                    if (completedTask == receiveTask)
+                    {
+                        var result = receiveTask.Result;
+                        await HandleMessageAsync(result.Buffer, result.RemoteEndPoint, udp, registeredHosts);
+                    }
+                }
+            }
+            catch (OperationCanceledException) { }
+            finally
+            {
+                udp.Close();
+                Console.WriteLine("Server shutting down");
+            }
+        }
+
+        static async Task HandleMessageAsync(byte[] buffer, IPEndPoint sender, UdpClient udp, Dictionary<IPEndPoint, AvailableGame> registeredHosts)
+        {
+            if (buffer.Length == 0) return;
+            var action = buffer[0];
+            var ms = new System.IO.MemoryStream(buffer, 1, buffer.Length - 1);
+            var reader = new System.IO.BinaryReader(ms, Encoding.UTF8);
+            switch (action)
+            {
+                case 0: // Register new game
+                    if (!registeredHosts.ContainsKey(sender))
+                    {
+                        var game = new AvailableGame
+                        {
+                            Count = reader.ReadInt32(),
+                            GamerTag = reader.ReadString(),
+                            PrivateGamerSlots = reader.ReadInt32(),
+                            MaxGamers = reader.ReadInt32(),
+                            IsHost = reader.ReadBoolean(),
+                            InternalIP = ReadIPEndPoint(reader),
+                            ExternalIP = sender,
+                            Game = reader.ReadString()
+                        };
+                        registeredHosts.Add(game.ExternalIP, game);
+                        Console.WriteLine($"Got registration for host {game}");
+                    }
+                    break;
+                case 1: // Client wants list of registered hosts
+                    string appid = reader.ReadString();
+                    Console.WriteLine($"Sending list of {registeredHosts.Count} hosts to client {sender}");
+                    foreach (var g1 in registeredHosts.Values)
+                    {
+                        if (g1.Game == appid)
+                        {
+                            var om = new System.IO.MemoryStream();
+                            var w = new System.IO.BinaryWriter(om, Encoding.UTF8);
+                            w.Write((byte)1); // response type
+                            w.Write(g1.Count);
+                            w.Write(g1.GamerTag);
+                            w.Write(g1.PrivateGamerSlots);
+                            w.Write(g1.MaxGamers);
+                            w.Write(g1.IsHost);
+                            WriteIPEndPoint(w, g1.InternalIP);
+                            WriteIPEndPoint(w, g1.ExternalIP);
+                            await udp.SendAsync(om.ToArray(), (int)om.Length, sender);
+                        }
+                    }
+                    break;
+                case 2: // Client wants to connect to a specific host
+                    var clientInternal = ReadIPEndPoint(reader);
+                    var hostExternal = ReadIPEndPoint(reader);
+                    var token = reader.ReadString();
+                    Console.WriteLine($"{sender} requesting introduction to {hostExternal} (token {token})");
+                    foreach (var elist in registeredHosts.Values)
+                    {
+                        if (elist.ExternalIP.Equals(hostExternal))
+                        {
+                            Console.WriteLine("Sending introduction...");
+                            // Send introduction to both client and host
+                            var om = new System.IO.MemoryStream();
+                            var w = new System.IO.BinaryWriter(om, Encoding.UTF8);
+                            w.Write((byte)2); // response type
+                            WriteIPEndPoint(w, elist.InternalIP);
+                            WriteIPEndPoint(w, elist.ExternalIP);
+                            WriteIPEndPoint(w, clientInternal);
+                            WriteIPEndPoint(w, sender);
+                            w.Write(token);
+                            await udp.SendAsync(om.ToArray(), (int)om.Length, sender);
+                        }
+                    }
+                    break;
+                case 3: // Remove host
+                    if (registeredHosts.ContainsKey(sender))
+                    {
+                        var game = registeredHosts[sender];
+                        var tag = reader.ReadString();
+                        var gamename = reader.ReadString();
+                        if (game.GamerTag == tag)
+                        {
+                            Console.WriteLine($"Remove for host {game.ExternalIP}");
+                            registeredHosts.Remove(game.ExternalIP);
+                        }
+                    }
+                    break;
+                case 4: // Update host
+                    if (registeredHosts.ContainsKey(sender))
+                    {
+                        var game = registeredHosts[sender];
+                        var count = reader.ReadInt32();
+                        var tag = reader.ReadString();
+                        if (game.GamerTag == tag)
+                        {
+                            Console.WriteLine($"Update for host {game.ExternalIP}");
+                            game.Count = count;
+                            game.PrivateGamerSlots = reader.ReadInt32();
+                            game.MaxGamers = reader.ReadInt32();
+                            game.IsHost = reader.ReadBoolean();
+                            game.InternalIP = ReadIPEndPoint(reader);
+                            game.Game = reader.ReadString();
+                        }
+                    }
+                    break;
+            }
+        }
+
+        static IPEndPoint ReadIPEndPoint(System.IO.BinaryReader reader)
+        {
+            var ipLen = reader.ReadInt32();
+            var ipBytes = reader.ReadBytes(ipLen);
+            var ip = new IPAddress(ipBytes);
+            var port = reader.ReadInt32();
+            return new IPEndPoint(ip, port);
+        }
+
+        static void WriteIPEndPoint(System.IO.BinaryWriter writer, IPEndPoint ep)
+        {
+            var ipBytes = ep.Address.GetAddressBytes();
+            writer.Write(ipBytes.Length);
+            writer.Write(ipBytes);
+            writer.Write(ep.Port);
+        }
+    }
+
+    class AvailableGame
+    {
+        public IPEndPoint ExternalIP { get; set; }
+        public IPEndPoint InternalIP { get; set; }
+        public int Count { get; set; }
+        public string GamerTag { get; set; }
+        public int PrivateGamerSlots { get; set; }
+        public int MaxGamers { get; set; }
+        public bool IsHost { get; set; }
+        public string Game { get; set; }
+
+        public override string ToString()
+        {
+            return $"External {ExternalIP}\n Internal {InternalIP} GamerTag {GamerTag}\n";
+        }
+    }
+}

+ 3 - 0
Peer2PeerSample/Peer2PeerMasterServer/app.config

@@ -0,0 +1,3 @@
+<?xml version="1.0"?>
+<configuration>
+<startup><supportedRuntime version="v2.0.50727"/></startup></configuration>

+ 0 - 68
Peer2PeerSample/Peer2PeerSample.Linux.csproj

@@ -1,68 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <PropertyGroup>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
-    <ProductVersion>9.0.21022</ProductVersion>
-    <SchemaVersion>2.0</SchemaVersion>
-    <ProjectGuid>{86A619E2-C8D0-452B-B762-C69D5BB07C98}</ProjectGuid>
-    <OutputType>Exe</OutputType>
-    <RootNamespace>Peer2PeerSample</RootNamespace>
-    <AssemblyName>Peer2PeerSample</AssemblyName>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
-    <DebugSymbols>True</DebugSymbols>
-    <DebugType>full</DebugType>
-    <Optimize>False</Optimize>
-    <OutputPath>bin\Debug</OutputPath>
-    <DefineConstants>DEBUG</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <PlatformTarget>x86</PlatformTarget>
-    <ConsolePause>False</ConsolePause>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
-    <DebugType>none</DebugType>
-    <Optimize>False</Optimize>
-    <OutputPath>bin\Release</OutputPath>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <PlatformTarget>x86</PlatformTarget>
-    <ConsolePause>False</ConsolePause>
-  </PropertyGroup>
-  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
-  <ItemGroup>
-    <Reference Include="System" />
-    <Reference Include="System.Xml" />
-    <Reference Include="System.Core" />
-    <Reference Include="System.Xml.Linq" />
-    <Reference Include="System.Drawing" />
-  </ItemGroup>
-  <ItemGroup>
-    <Compile Include="PeerToPeerGame.cs">
-      <Link>PeerToPeerGame.cs</Link>
-    </Compile>
-    <Compile Include="Tank.cs">
-      <Link>Tank.cs</Link>
-    </Compile>
-    <Compile Include="Program.cs" />
-  </ItemGroup>
-  <ItemGroup>
-    <Content Include="Content\Font.spritefont">
-      <Link>Content\Font.spritefont</Link>
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="Content\Font.xnb">
-      <Link>Content\Font.xnb</Link>
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="Content\Tank.xnb">
-      <Link>Content\Tank.xnb</Link>
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="Content\Turret.xnb">
-      <Link>Content\Turret.xnb</Link>
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-  </ItemGroup>
-</Project>

+ 0 - 82
Peer2PeerSample/Peer2PeerSample.MacOS.csproj

@@ -1,82 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <PropertyGroup>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
-    <ProductVersion>10.0.0</ProductVersion>
-    <SchemaVersion>2.0</SchemaVersion>
-    <ProjectGuid>{27C1B777-4E37-4E2B-9CE4-5FDEE8CD9C40}</ProjectGuid>
-    <ProjectTypeGuids>{948B3504-5B70-4649-8FE4-BDE1FB46EC69};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
-    <OutputType>Exe</OutputType>
-    <RootNamespace>Peer2PeerSample</RootNamespace>
-    <AssemblyName>Peer2PeerSample</AssemblyName>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-    <DebugSymbols>True</DebugSymbols>
-    <DebugType>full</DebugType>
-    <Optimize>False</Optimize>
-    <OutputPath>bin\Debug</OutputPath>
-    <DefineConstants>DEBUG;MONOMAC</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <ConsolePause>False</ConsolePause>
-    <EnableCodeSigning>False</EnableCodeSigning>
-    <CreatePackage>False</CreatePackage>
-    <EnablePackageSigning>False</EnablePackageSigning>
-    <IncludeMonoRuntime>False</IncludeMonoRuntime>
-    <UseSGen>False</UseSGen>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-    <DebugType>none</DebugType>
-    <Optimize>False</Optimize>
-    <OutputPath>bin\Release</OutputPath>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <ConsolePause>False</ConsolePause>
-    <EnableCodeSigning>False</EnableCodeSigning>
-    <CreatePackage>False</CreatePackage>
-    <EnablePackageSigning>False</EnablePackageSigning>
-    <IncludeMonoRuntime>False</IncludeMonoRuntime>
-    <DefineConstants>MONOMAC</DefineConstants>
-    <UseSGen>False</UseSGen>
-  </PropertyGroup>
-  <ItemGroup>
-    <Reference Include="System" />
-    <Reference Include="System.Xml" />
-    <Reference Include="System.Core" />
-    <Reference Include="System.Xml.Linq" />
-    <Reference Include="System.Drawing" />
-    <Reference Include="MonoMac" />
-  </ItemGroup>
-  <ItemGroup>
-    <None Include="Info.plist">
-    </None>
-    <None Include="Content\Font.spritefont" />
-    <None Include="Content\Tank.tga" />
-    <None Include="Content\Turret.tga" />
-    <None Include="Game.ico" />
-    <None Include="PeerToPeer.png" />
-  </ItemGroup>
-  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
-  <Import Project="$(MSBuildExtensionsPath)\Mono\MonoMac\v0.0\Mono.MonoMac.targets" />
-  <ItemGroup>
-    <Content Include="Content\Font.xnb" />
-    <Content Include="Content\Tank.xnb" />
-    <Content Include="Content\Turret.xnb" />
-  </ItemGroup>
-  <ItemGroup>
-    <Compile Include="PeerToPeerGame.cs" />
-    <Compile Include="Tank.cs" />
-    <Compile Include="Program.cs" />
-  </ItemGroup>
-  <ItemGroup>
-    <ProjectReference Include="..\..\MonoGame.Framework\MonoGame.Framework.MacOS.csproj">
-      <Project>{36C538E6-C32A-4A8D-A39C-566173D7118E}</Project>
-      <Name>MonoGame.Framework.MacOS</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\ThirdParty\Lidgren.Network\Lidgren.Network.MacOS.csproj">
-      <Project>{AE483C29-042E-4226-BA52-D247CE7676DA}</Project>
-      <Name>Lidgren.Network.MacOS</Name>
-    </ProjectReference>
-  </ItemGroup>
-</Project>

+ 0 - 85
Peer2PeerSample/Peer2PeerSample.Windows.csproj

@@ -1,85 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build">
-  <PropertyGroup>
-    <ProjectGuid>{5FAEAF4E-75EC-41E4-8D01-EF1CBB9EEAB6}</ProjectGuid>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
-    <OutputType>Exe</OutputType>
-    <RootNamespace>Peer2PeerSample</RootNamespace>
-    <AssemblyName>Peer2PeerSample</AssemblyName>
-    <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
-    <AppDesignerFolder>Properties</AppDesignerFolder>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Platform)' == 'x86' ">
-    <PlatformTarget>x86</PlatformTarget>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
-    <OutputPath>bin\Debug\</OutputPath>
-    <DebugSymbols>True</DebugSymbols>
-    <DebugType>Full</DebugType>
-    <Optimize>False</Optimize>
-    <CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>
-    <DefineConstants>DEBUG;TRACE</DefineConstants>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
-    <OutputPath>bin\Release\</OutputPath>
-    <DebugSymbols>False</DebugSymbols>
-    <DebugType>None</DebugType>
-    <Optimize>True</Optimize>
-    <CheckForOverflowUnderflow>False</CheckForOverflowUnderflow>
-    <DefineConstants>TRACE</DefineConstants>
-  </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
-    <DefineConstants>DEBUG;TRACE</DefineConstants>
-  </PropertyGroup>
-  <ItemGroup>
-    <Reference Include="System" />
-    <Reference Include="System.Core">
-      <RequiredTargetFramework>3.5</RequiredTargetFramework>
-    </Reference>
-    <Reference Include="System.Data" />
-    <Reference Include="System.Data.DataSetExtensions">
-      <RequiredTargetFramework>3.5</RequiredTargetFramework>
-    </Reference>
-    <Reference Include="System.Xml" />
-    <Reference Include="System.Xml.Linq">
-      <RequiredTargetFramework>3.5</RequiredTargetFramework>
-    </Reference>
-  </ItemGroup>
-  <ItemGroup>
-    <Compile Include="PeerToPeerGame.cs">
-      <Link>PeerToPeerGame.cs</Link>
-    </Compile>
-    <Compile Include="Tank.cs">
-      <Link>Tank.cs</Link>
-    </Compile>
-    <Compile Include="Program.cs" />
-    <Compile Include="Properties\AssemblyInfo.cs" />
-  </ItemGroup>
-  <ItemGroup>
-    <Content Include="Content\Font.spritefont">
-      <Link>Content\Font.spritefont</Link>
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="Content\Font.xnb">
-      <Link>Content\Font.xnb</Link>
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="Content\Tank.xnb">
-      <Link>Content\Tank.xnb</Link>
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="Content\Turret.xnb">
-      <Link>Content\Turret.xnb</Link>
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <None Include="app.config" />
-  </ItemGroup>
-  <ItemGroup>
-    <ProjectReference Include="..\..\MonoGame.Framework\MonoGame.Framework.Windows.csproj">
-      <Project>{7DE47032-A904-4C29-BD22-2D235E8D91BA}</Project>
-      <Name>MonoGame.Framework.Windows</Name>
-    </ProjectReference>
-  </ItemGroup>
-  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.Targets" />
-</Project>

+ 41 - 20
Peer2PeerSample/Peer2PeerSample.sln

@@ -1,37 +1,58 @@
 
-Microsoft Visual Studio Solution File, Format Version 11.00
-# Visual Studio 2010
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Peer2PeerSample", "Peer2PeerSample.csproj", "{27C1B777-4E37-4E2B-9CE4-5FDEE8CD9C40}"
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Peer2PeerSample.Windows", "Platforms\Windows\Peer2PeerSample.Windows.csproj", "{5FAEAF4E-75EC-41E4-8D01-EF1CBB9EEAB6}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoGame.Framework.MacOS", "..\..\..\..\..\..\..\Users\Jimmy\Public\Share\MonoMacSource\kjpgit\MonoGame\MonoGame.Framework\MonoGame.Framework.MacOS.csproj", "{36C538E6-C32A-4A8D-A39C-566173D7118E}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Peer2PeerSample.DesktopGL", "Platforms\Desktop\Peer2PeerSample.DesktopGL.csproj", "{27C1B777-4E37-4E2B-9CE4-5FDEE8CD9C40}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lidgren.Network", "..\..\..\lidgren-network-gen3\Lidgren.Network\Lidgren.Network.csproj", "{AE483C29-042E-4226-BA52-D247CE7676DA}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Peer2Peer.Android", "Platforms\Android\Peer2Peer.Android.csproj", "{56F6C06F-6F07-402E-89A5-39EDB037A516}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Peer2Peer.iOS", "Platforms\iOS\Peer2Peer.iOS.csproj", "{1217FD6D-AFF5-4A21-AA0C-0AE7F14B848F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Peer2PeerSample.Core", "Core\Peer2PeerSample.Core.csproj", "{A1B2C3D4-E5F6-7890-1234-56789ABCDEF0}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoGame.Xna.Framework.Net", "..\MonoGame.Xna.Framework.Net\MonoGame.Xna.Framework.Net.csproj", "{86E1E7AD-DD62-384D-A073-092A5F875CF0}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Peer2PeerMasterServer", "Peer2PeerMasterServer\Peer2PeerMasterServer.csproj", "{5B387405-901A-2547-BA97-DDF3180E221C}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
 		Release|Any CPU = Release|Any CPU
-		Distribution|Any CPU = Distribution|Any CPU
 	EndGlobalSection
 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{5FAEAF4E-75EC-41E4-8D01-EF1CBB9EEAB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{5FAEAF4E-75EC-41E4-8D01-EF1CBB9EEAB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{5FAEAF4E-75EC-41E4-8D01-EF1CBB9EEAB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{5FAEAF4E-75EC-41E4-8D01-EF1CBB9EEAB6}.Release|Any CPU.Build.0 = Release|Any CPU
 		{27C1B777-4E37-4E2B-9CE4-5FDEE8CD9C40}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{27C1B777-4E37-4E2B-9CE4-5FDEE8CD9C40}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{27C1B777-4E37-4E2B-9CE4-5FDEE8CD9C40}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{27C1B777-4E37-4E2B-9CE4-5FDEE8CD9C40}.Release|Any CPU.Build.0 = Release|Any CPU
-		{36C538E6-C32A-4A8D-A39C-566173D7118E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{36C538E6-C32A-4A8D-A39C-566173D7118E}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{36C538E6-C32A-4A8D-A39C-566173D7118E}.Distribution|Any CPU.ActiveCfg = Distribution|Any CPU
-		{36C538E6-C32A-4A8D-A39C-566173D7118E}.Distribution|Any CPU.Build.0 = Distribution|Any CPU
-		{36C538E6-C32A-4A8D-A39C-566173D7118E}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{36C538E6-C32A-4A8D-A39C-566173D7118E}.Release|Any CPU.Build.0 = Release|Any CPU
-		{AE483C29-042E-4226-BA52-D247CE7676DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{AE483C29-042E-4226-BA52-D247CE7676DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{AE483C29-042E-4226-BA52-D247CE7676DA}.Distribution|Any CPU.ActiveCfg = Debug|Any CPU
-		{AE483C29-042E-4226-BA52-D247CE7676DA}.Distribution|Any CPU.Build.0 = Debug|Any CPU
-		{AE483C29-042E-4226-BA52-D247CE7676DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{AE483C29-042E-4226-BA52-D247CE7676DA}.Release|Any CPU.Build.0 = Release|Any CPU
+		{56F6C06F-6F07-402E-89A5-39EDB037A516}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{56F6C06F-6F07-402E-89A5-39EDB037A516}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{56F6C06F-6F07-402E-89A5-39EDB037A516}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{56F6C06F-6F07-402E-89A5-39EDB037A516}.Release|Any CPU.Build.0 = Release|Any CPU
+		{1217FD6D-AFF5-4A21-AA0C-0AE7F14B848F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{1217FD6D-AFF5-4A21-AA0C-0AE7F14B848F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{1217FD6D-AFF5-4A21-AA0C-0AE7F14B848F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{1217FD6D-AFF5-4A21-AA0C-0AE7F14B848F}.Release|Any CPU.Build.0 = Release|Any CPU
+		{A1B2C3D4-E5F6-7890-1234-56789ABCDEF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{A1B2C3D4-E5F6-7890-1234-56789ABCDEF0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{A1B2C3D4-E5F6-7890-1234-56789ABCDEF0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{A1B2C3D4-E5F6-7890-1234-56789ABCDEF0}.Release|Any CPU.Build.0 = Release|Any CPU
+		{86E1E7AD-DD62-384D-A073-092A5F875CF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{86E1E7AD-DD62-384D-A073-092A5F875CF0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{86E1E7AD-DD62-384D-A073-092A5F875CF0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{86E1E7AD-DD62-384D-A073-092A5F875CF0}.Release|Any CPU.Build.0 = Release|Any CPU
+		{5B387405-901A-2547-BA97-DDF3180E221C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{5B387405-901A-2547-BA97-DDF3180E221C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{5B387405-901A-2547-BA97-DDF3180E221C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{5B387405-901A-2547-BA97-DDF3180E221C}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
-	GlobalSection(MonoDevelopProperties) = preSolution
-		StartupItem = Peer2PeerSample.csproj
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
 	EndGlobalSection
 EndGlobal

+ 0 - 572
Peer2PeerSample/PeerToPeerGame.cs

@@ -1,572 +0,0 @@
-#region File Description
-//-----------------------------------------------------------------------------
-// PeerToPeerGame.cs
-//
-// Microsoft XNA Community Game Platform
-// Copyright (C) Microsoft Corporation. All rights reserved.
-//-----------------------------------------------------------------------------
-#endregion
-
-#region Using Statements
-using System;
-
-#if ANDROID
-using Android.App;
-#endif
-
-using Microsoft.Xna.Framework;
-using Microsoft.Xna.Framework.GamerServices;
-using Microsoft.Xna.Framework.Graphics;
-using Microsoft.Xna.Framework.Input;
-using Microsoft.Xna.Framework.Input.Touch;
-using Microsoft.Xna.Framework.Net;
-
-#endregion
-
-namespace PeerToPeer
-{
-	/// <summary>
-	/// Sample showing how to implement a simple multiplayer
-	/// network session, using a peer-to-peer network topology.
-	/// </summary>
-	public class PeerToPeerGame : Microsoft.Xna.Framework.Game
-	{
-	#region Fields
-
-		const int screenWidth = 1067;
-		const int screenHeight = 600;
-		const int maxGamers = 16;
-		const int maxLocalGamers = 4;
-		GraphicsDeviceManager graphics;
-		SpriteBatch spriteBatch;
-		SpriteFont font;
-		KeyboardState currentKeyboardState;
-		GamePadState currentGamePadState;
-		TouchCollection currentTouchState;
-		NetworkSession networkSession;
-		PacketWriter packetWriter = new PacketWriter ();
-		PacketReader packetReader = new PacketReader ();
-		string errorMessage;
-		Texture2D gamePadTexture;
-
-	#endregion
-
-	#region Initialization
-
-
-        public PeerToPeerGame ()  
-		{
-			graphics = new GraphicsDeviceManager (this);
-			
-#if ANDROID || IPHONE
-            graphics.IsFullScreen = true;
-#else
-			graphics.PreferredBackBufferWidth = screenWidth;
-			graphics.PreferredBackBufferHeight = screenHeight;
-			graphics.IsFullScreen = false;
-#endif
-
-			Content.RootDirectory = "Content";
-
-			Components.Add (new GamerServicesComponent (this));
-		}
-
-
-		/// <summary>
-		/// Load your content.
-		/// </summary>
-		protected override void LoadContent ()
-		{
-			spriteBatch = new SpriteBatch (GraphicsDevice);
-
-			font = Content.Load<SpriteFont> ("Font");
-
-#if ANDROID || IPHONE
-			gamePadTexture = Content.Load<Texture2D>("gamepad.png");
-			
-			ThumbStickDefinition thumbStickLeft = new ThumbStickDefinition();
-			thumbStickLeft.Position = new Vector2(10,400);
-			thumbStickLeft.Texture = gamePadTexture;
-			thumbStickLeft.TextureRect = new Rectangle(2,2,68,68);
-			
-			GamePad.LeftThumbStickDefinition = thumbStickLeft;
-			
-			ThumbStickDefinition thumbStickRight = new ThumbStickDefinition();
-			thumbStickRight.Position = new Vector2(240,400);
-			thumbStickRight.Texture = gamePadTexture;
-			thumbStickRight.TextureRect = new Rectangle(2,2,68,68);
-			
-			GamePad.RightThumbStickDefinition = thumbStickRight;
-#endif
-		}
-
-
-	#endregion
-
-	#region Update
-
-
-		/// <summary>
-		/// Allows the game to run logic.
-		/// </summary>
-		protected override void Update (GameTime gameTime)
-		{
-			HandleInput ();
-
-			if (networkSession == null) {
-				// If we are not in a network session, update the
-				// menu screen that will let us create or join one.
-				UpdateMenuScreen ();
-			} else {
-				// If we are in a network session, update it.
-				UpdateNetworkSession ();
-			}
-
-			base.Update (gameTime);
-		}
-
-
-		/// <summary>
-		/// Menu screen provides options to create or join network sessions.
-		/// </summary>
-		void UpdateMenuScreen ()
-		{
-			if (IsActive) {
-				if (Gamer.SignedInGamers.Count == 0) {
-					// If there are no profiles signed in, we cannot proceed.
-					// Show the Guide so the user can sign in.
-					Guide.ShowSignIn (maxLocalGamers, false);
-				} else if (IsPressed (Keys.A, Buttons.A)) {
-					// Create a new session?
-					CreateSession ();
-                } else if (IsPressed(Keys.X, Buttons.X)) {
-                    CreateLiveSession();
-                }
-                else if (IsPressed(Keys.Y, Buttons.Y))
-                {
-                    JoinSession(NetworkSessionType.PlayerMatch);
-				} else if (IsPressed (Keys.B, Buttons.B)) {
-					// Join an existing session?
-					JoinSession (NetworkSessionType.SystemLink);
-				}
-			}
-		}
-
-        private void JoinLiveSession()
-        {
-            throw new NotImplementedException();
-        }
-
-        private void CreateLiveSession()
-        {
-            DrawMessage("Creating Live session...");
-
-            try
-            {
-                networkSession = NetworkSession.Create(NetworkSessionType.PlayerMatch,
-                            maxLocalGamers, maxGamers);
-
-                HookSessionEvents();
-                //networkSession.AddLocalGamer();
-            }
-            catch (Exception e)
-            {
-                errorMessage = e.Message;
-            }
-        }
-
-
-		/// <summary>
-		/// Starts hosting a new network session.
-		/// </summary>
-		void CreateSession ()
-		{
-			DrawMessage ("Creating session...");
-
-			try {
-				networkSession = NetworkSession.Create (NetworkSessionType.SystemLink, 
-							maxLocalGamers, maxGamers);
-
-				HookSessionEvents ();
-				//networkSession.AddLocalGamer();
-			} catch (Exception e) {
-				errorMessage = e.Message;
-			}
-		}
-
-
-		/// <summary>
-		/// Joins an existing network session.
-		/// </summary>
-		void JoinSession (NetworkSessionType type)
-		{
-			DrawMessage ("Joining session...");
-
-			try {
-				// Search for sessions.
-				using (AvailableNetworkSessionCollection availableSessions =
-				NetworkSession.Find (type, 
-						maxLocalGamers, null)) {
-					if (availableSessions.Count == 0) {
-						errorMessage = "No network sessions found.";
-						return;
-					}
-
-					// Join the first session we found.
-					networkSession = NetworkSession.Join (availableSessions [0]);
-
-					HookSessionEvents ();
-				}
-			} catch (Exception e) {
-				errorMessage = e.Message;
-			}
-		}
-
-
-		/// <summary>
-		/// After creating or joining a network session, we must subscribe to
-		/// some events so we will be notified when the session changes state.
-		/// </summary>
-		void HookSessionEvents ()
-		{
-			networkSession.GamerJoined += GamerJoinedEventHandler;
-			networkSession.SessionEnded += SessionEndedEventHandler;
-		}
-
-
-		/// <summary>
-		/// This event handler will be called whenever a new gamer joins the session.
-		/// We use it to allocate a Tank object, and associate it with the new gamer.
-		/// </summary>
-		void GamerJoinedEventHandler (object sender, GamerJoinedEventArgs e)
-		{
-			int gamerIndex = networkSession.AllGamers.IndexOf (e.Gamer);
-
-			e.Gamer.Tag = new Tank (gamerIndex, Content, screenWidth, screenHeight);
-		}
-
-
-		/// <summary>
-		/// Event handler notifies us when the network session has ended.
-		/// </summary>
-		void SessionEndedEventHandler (object sender, NetworkSessionEndedEventArgs e)
-		{
-			errorMessage = e.EndReason.ToString ();
-
-			networkSession.Dispose ();
-			networkSession = null;
-		}
-
-
-		/// <summary>
-		/// Updates the state of the network session, moving the tanks
-		/// around and synchronizing their state over the network.
-		/// </summary>
-		void UpdateNetworkSession ()
-		{
-			// Update our locally controlled tanks, and send their
-			// latest position data to everyone in the session.
-			foreach (LocalNetworkGamer gamer in networkSession.LocalGamers) {
-				UpdateLocalGamer (gamer);
-			}
-
-			// Pump the underlying session object.
-			networkSession.Update ();
-
-			// Make sure the session has not ended.
-			if (networkSession == null)
-				return;
-
-			// Read any packets telling us the positions of remotely controlled tanks.
-			foreach (LocalNetworkGamer gamer in networkSession.LocalGamers) {
-				ReadIncomingPackets (gamer);
-			}
-		}
-
-
-		/// <summary>
-		/// Helper for updating a locally controlled gamer.
-		/// </summary>
-		void UpdateLocalGamer (LocalNetworkGamer gamer)
-		{
-			// Look up what tank is associated with this local player.
-			Tank localTank = gamer.Tag as Tank;
-
-            if (localTank != null)
-            {
-
-                // Update the tank.
-                ReadTankInputs(localTank, gamer.SignedInGamer.PlayerIndex);
-
-                localTank.Update();
-
-                // Write the tank state into a network packet.
-                packetWriter.Write(localTank.Position);
-                packetWriter.Write(localTank.TankRotation);
-                packetWriter.Write(localTank.TurretRotation);
-
-                // Send the data to everyone in the session.
-                gamer.SendData(packetWriter, SendDataOptions.InOrder);
-            }
-		}
-
-
-		/// <summary>
-		/// Helper for reading incoming network packets.
-		/// </summary>
-		void ReadIncomingPackets (LocalNetworkGamer gamer)
-		{
-			// Keep reading as long as incoming packets are available.
-			while (gamer.IsDataAvailable) {
-				NetworkGamer sender;
-
-				// Read a single packet from the network.
-				gamer.ReceiveData (packetReader, out sender);
-
-				// Discard packets sent by local gamers: we already know their state!
-				if (sender.IsLocal)
-					continue;
-
-				// Look up the tank associated with whoever sent this packet.
-				Tank remoteTank = sender.Tag as Tank;
-                if (remoteTank != null)
-                {
-
-                    // Read the state of this tank from the network packet.
-                    remoteTank.Position = packetReader.ReadVector2();
-                    remoteTank.TankRotation = packetReader.ReadSingle();
-                    remoteTank.TurretRotation = packetReader.ReadSingle();
-
-                }
-			}
-		}
-
-
-	#endregion
-
-	#region Draw
-
-
-		/// <summary>
-		/// This is called when the game should draw itself.
-		/// </summary>
-		protected override void Draw (GameTime gameTime)
-		{
-			GraphicsDevice.Clear (Color.CornflowerBlue);
-
-			if (networkSession == null) {
-				// If we are not in a network session, draw the
-				// menu screen that will let us create or join one.
-				DrawMenuScreen ();
-			} else {
-				// If we are in a network session, draw it.
-				DrawNetworkSession (gameTime);
-			}
-
-			base.Draw (gameTime);
-		}
-
-
-		/// <summary>
-		/// Draws the startup screen used to create and join network sessions.
-		/// </summary>
-		void DrawMenuScreen ()
-		{
-			string message = string.Empty;
-
-			if (!string.IsNullOrEmpty (errorMessage))
-				message += "Error:\n" + errorMessage.Replace (". ", ".\n") + "\n\n";
-
-			message += "A = create local session\n" +
-            "X = create live session\n" +
-            "Y = join live session\n" + 
-			"B = join session";
-
-			spriteBatch.Begin ();
-
-			spriteBatch.DrawString (font, message, new Vector2 (61, 161), Color.Black);
-			spriteBatch.DrawString (font, message, new Vector2 (60, 160), Color.White);
-
-			spriteBatch.End ();
-		}
-
-
-		/// <summary>
-		/// Draws the state of an active network session.
-		/// </summary>
-		void DrawNetworkSession (GameTime gameTime)
-		{
-			spriteBatch.Begin ();
-
-			// For each person in the session...
-			foreach (NetworkGamer gamer in networkSession.AllGamers) {
-				// Look up the tank object belonging to this network gamer.
-				Tank tank = gamer.Tag as Tank;
-
-                if (tank != null)
-                {
-                    // Draw the tank.
-                    tank.Draw(spriteBatch);
-
-                    // Draw a gamertag label.
-                    string label = gamer.Gamertag;
-                    Color labelColor = Color.Black;
-                    Vector2 labelOffset = new Vector2(100, 150);
-
-                    if (gamer.IsHost)
-                        label += " (host)";
-
-                    // Flash the gamertag to yellow when the player is talking.
-                    if (gamer.IsTalking)
-                        labelColor = Color.Yellow;
-
-                    spriteBatch.DrawString(font, label, tank.Position, labelColor, 0,
-                        labelOffset, 0.6f, SpriteEffects.None, 0);
-                }
-			}
-			
-#if ANDROID || IPHONE
-			GamePad.Draw(gameTime, spriteBatch);
-#endif
-			
-			spriteBatch.End ();
-		}
-
-
-		/// <summary>
-		/// Helper draws notification messages before calling blocking network methods.
-		/// </summary>
-		void DrawMessage (string message)
-		{
-			if (!BeginDraw ())
-				return;
-
-			GraphicsDevice.Clear (Color.CornflowerBlue);
-
-			spriteBatch.Begin ();
-
-			spriteBatch.DrawString (font, message, new Vector2 (161, 161), Color.Black);
-			spriteBatch.DrawString (font, message, new Vector2 (160, 160), Color.White);
-
-			spriteBatch.End ();
-
-			EndDraw ();
-		}
-
-
-	#endregion
-
-	#region Handle Input
-
-
-		/// <summary>
-		/// Handles input.
-		/// </summary>
-		private void HandleInput ()
-		{
-			currentKeyboardState = Keyboard.GetState ();
-			currentGamePadState = GamePad.GetState (PlayerIndex.One);
-			currentTouchState = TouchPanel.GetState();
-
-			// Check for exit.
-			if (IsActive && IsPressed (Keys.Escape, Buttons.Back)) {
-				Exit ();
-			}
-			
-			// Only test of Menu touches when networkSession is null
-			if (networkSession == null) 
-			{
-				// Doing very very basic touch detection for menu
-				if (currentTouchState.Count > 0) 
-				{
-					Console.WriteLine( string.Format("X:{0}, Y:{1}", currentTouchState[0].Position.X, currentTouchState[0].Position.Y ) );
-					if ((currentTouchState[0].Position.X > 60 ) && (currentTouchState[0].Position.Y > 160 )
-					&& (currentTouchState[0].Position.X < 220 ) && (currentTouchState[0].Position.Y < 190 ) )
-					{
-						CreateSession ();
-					}
-					
-					if ((currentTouchState[0].Position.X > 60 ) && (currentTouchState[0].Position.Y > 200 )
-					&& (currentTouchState[0].Position.X < 220 ) && (currentTouchState[0].Position.Y < 230 ) )
-					{
-						CreateLiveSession();
-					}
-					
-					if ((currentTouchState[0].Position.X > 60 ) && (currentTouchState[0].Position.Y > 240 )
-					&& (currentTouchState[0].Position.X < 220 ) && (currentTouchState[0].Position.Y < 270 ) )
-					{
-						JoinSession(NetworkSessionType.PlayerMatch);
-					}
-					
-					if ((currentTouchState[0].Position.X > 60 ) && (currentTouchState[0].Position.Y > 280 )
-					&& (currentTouchState[0].Position.X < 220 ) && (currentTouchState[0].Position.Y < 310 ) )
-					{
-						JoinSession(NetworkSessionType.SystemLink);
-					}
-				}
-			}
-			
-		}
-
-
-		/// <summary>
-		/// Checks if the specified button is pressed on either keyboard or gamepad.
-		/// </summary>
-		bool IsPressed (Keys key, Buttons button)
-		{
-			return (currentKeyboardState.IsKeyDown (key) || 
-			currentGamePadState.IsButtonDown (button));
-		}
-
-
-		/// <summary>
-		/// Reads input data from keyboard and gamepad, and stores
-		/// it into the specified tank object.
-		/// </summary>
-		void ReadTankInputs (Tank tank, PlayerIndex playerIndex)
-		{
-			// Read the gamepad.
-			GamePadState gamePad = GamePad.GetState (playerIndex);
-
-			Vector2 tankInput = gamePad.ThumbSticks.Left;
-			Vector2 turretInput = gamePad.ThumbSticks.Right;
-
-			// Read the keyboard.
-			KeyboardState keyboard = Keyboard.GetState (playerIndex);
-
-			if (keyboard.IsKeyDown (Keys.Left))
-				tankInput.X = -1;
-			else if (keyboard.IsKeyDown (Keys.Right))
-				tankInput.X = 1;
-
-			if (keyboard.IsKeyDown (Keys.Up))
-				tankInput.Y = 1;
-			else if (keyboard.IsKeyDown (Keys.Down))
-				tankInput.Y = -1;
-
-			if (keyboard.IsKeyDown (Keys.A))
-				turretInput.X = -1;
-			else if (keyboard.IsKeyDown (Keys.D))
-				turretInput.X = 1;
-
-			if (keyboard.IsKeyDown (Keys.W))
-				turretInput.Y = 1;
-			else if (keyboard.IsKeyDown (Keys.S))
-				turretInput.Y = -1;
-
-			// Normalize the input vectors.
-			if (tankInput.Length () > 1)
-				tankInput.Normalize ();
-
-			if (turretInput.Length () > 1)
-				turretInput.Normalize ();
-
-			// Store these input values into the tank object.
-			tank.TankInput = tankInput;
-			tank.TurretInput = turretInput;
-		}
-
-
-	#endregion
-	}
-}

+ 30 - 0
Peer2PeerSample/Platforms/Android/AndroidManifest.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
+          package="com.peer2peer.sample" 
+          android:versionCode="1" 
+          android:versionName="1.0">
+  
+  <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" />
+  
+  <uses-permission android:name="android.permission.INTERNET" />
+  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+  <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+  <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+  
+  <application android:label="Peer2Peer Sample" 
+               android:icon="@drawable/icon"
+               android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+    
+    <activity android:name="peer2peer.Activity1" 
+              android:label="Peer2Peer Sample"
+              android:exported="true"
+              android:screenOrientation="landscape"
+              android:configChanges="keyboard|keyboardHidden|orientation|screenSize">
+      <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.LAUNCHER" />
+      </intent-filter>
+    </activity>
+    
+  </application>
+</manifest>

+ 23 - 0
Peer2PeerSample/Platforms/Android/MainActivity.cs

@@ -0,0 +1,23 @@
+using Android.App;
+using Android.Content.PM;
+using Android.OS;
+using Microsoft.Xna.Framework;
+
+namespace PeerToPeer.Android
+{
+    [Activity(Label = "Peer2Peer", MainLauncher = true,
+        ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden,
+        Icon = "@drawable/icon",
+        Theme = "@style/Theme.Splash",
+        NoHistory = true)]
+    public class MainActivity : AndroidGameActivity
+    {
+        protected override void OnCreate(Bundle bundle)
+        {
+            base.OnCreate(bundle);
+            var game = new PeerToPeerGame();
+            SetContentView(game.Window);
+            game.Run();
+        }
+    }
+}

+ 29 - 0
Peer2PeerSample/Platforms/Android/Peer2Peer.Android.csproj

@@ -0,0 +1,29 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0-android</TargetFramework>
+    <OutputType>Exe</OutputType>
+    <RootNamespace>PeerToPeer</RootNamespace>
+    <AssemblyName>Peer2PeerSample</AssemblyName>
+    <AndroidApplication>true</AndroidApplication>
+    <AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
+    <AndroidManifest>AndroidManifest.xml</AndroidManifest>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <Nullable>disable</Nullable>
+    <SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
+    <AndroidUseAapt2>true</AndroidUseAapt2>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Framework.Android" Version="3.8.*" />
+    <PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.*" />
+    <ProjectReference Include="..\..\Core\Peer2PeerSample.Core.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Content Include="..\..\Core\Content\**\*.xnb" Link="Content\%(RecursiveDir)%(Filename)%(Extension)">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+  </ItemGroup>
+
+</Project>

+ 0 - 0
Peer2PeerSample/Resources/Drawable/Icon.png → Peer2PeerSample/Platforms/Android/Resources/Drawable/Icon.png


+ 0 - 0
Peer2PeerSample/Resources/Drawable/Splash.png → Peer2PeerSample/Platforms/Android/Resources/Drawable/Splash.png


+ 0 - 0
Peer2PeerSample/Resources/Resource.Designer.cs → Peer2PeerSample/Platforms/Android/Resources/Resource.Designer.cs


+ 0 - 0
Peer2PeerSample/Resources/Values/Styles.xml → Peer2PeerSample/Platforms/Android/Resources/Values/Styles.xml


+ 25 - 0
Peer2PeerSample/Platforms/Desktop/Peer2PeerSample.DesktopGL.csproj

@@ -0,0 +1,25 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net8.0</TargetFramework>
+    <RootNamespace>PeerToPeer</RootNamespace>
+    <AssemblyName>Peer2PeerSample</AssemblyName>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <Nullable>disable</Nullable>
+    <ApplicationIcon>..\..\Core\Content\Game.ico</ApplicationIcon>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.*" />
+    <PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.*" />
+    <ProjectReference Include="..\..\Core\Peer2PeerSample.Core.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Content Include="..\..\Core\Content\**\*.xnb" Link="Content\%(RecursiveDir)%(Filename)%(Extension)">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+  </ItemGroup>
+
+</Project>

+ 14 - 0
Peer2PeerSample/Platforms/Desktop/Program.cs

@@ -0,0 +1,14 @@
+using System;
+
+namespace PeerToPeer.Desktop
+{
+    public static class Program
+    {
+        [STAThread]
+        static void Main()
+        {
+            using (var game = new PeerToPeerGame())
+                game.Run();
+        }
+    }
+}

+ 26 - 0
Peer2PeerSample/Platforms/Windows/Peer2PeerSample.Windows.csproj

@@ -0,0 +1,26 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>WinExe</OutputType>
+    <TargetFramework>net8.0-windows</TargetFramework>
+    <RootNamespace>PeerToPeer</RootNamespace>
+    <AssemblyName>Peer2PeerSample</AssemblyName>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <Nullable>disable</Nullable>
+    <ApplicationIcon>..\..\Core\Content\Game.ico</ApplicationIcon>
+    <UseWindowsForms>true</UseWindowsForms>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Framework.WindowsDX" Version="3.8.*" />
+    <PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.*" />
+    <ProjectReference Include="..\..\Core\Peer2PeerSample.Core.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Content Include="..\..\Core\Content\**\*.xnb" Link="Content\%(RecursiveDir)%(Filename)%(Extension)">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+  </ItemGroup>
+
+</Project>

+ 22 - 0
Peer2PeerSample/Platforms/Windows/Program.cs

@@ -0,0 +1,22 @@
+//-----------------------------------------------------------------------------
+// PeerToPeerGame.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+
+using System;
+
+namespace PeerToPeer.Windows
+{
+	class Program
+	{
+		public static void Main(string[] args)
+		{
+			using (var game = new PeerToPeerGame())
+			{
+				game.Run();
+			}
+		}
+	}
+}

+ 21 - 0
Peer2PeerSample/Platforms/iOS/AppDelegate.cs

@@ -0,0 +1,21 @@
+using Foundation;
+using UIKit;
+using Microsoft.Xna.Framework;
+
+namespace PeerToPeer.iOS
+{
+    [Register("AppDelegate")]
+    public class AppDelegate : UIApplicationDelegate
+    {
+        private Game game;
+
+        public override UIWindow Window { get; set; }
+
+        public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
+        {
+            game = new PeerToPeerGame();
+            game.Run();
+            return true;
+        }
+    }
+}

+ 0 - 0
Peer2PeerSample/Info.plist → Peer2PeerSample/Platforms/iOS/Info.plist


+ 25 - 0
Peer2PeerSample/Platforms/iOS/Peer2Peer.iOS.csproj

@@ -0,0 +1,25 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0-ios</TargetFramework>
+    <OutputType>Exe</OutputType>
+    <RootNamespace>PeerToPeer</RootNamespace>
+    <AssemblyName>Peer2PeerSample</AssemblyName>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <Nullable>disable</Nullable>
+    <SupportedOSPlatformVersion>11.0</SupportedOSPlatformVersion>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Framework.iOS" Version="3.8.*" />
+    <PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.*" />
+    <ProjectReference Include="..\..\Core\Peer2PeerSample.Core.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Content Include="..\..\Core\Content\**\*.xnb" Link="Content\%(RecursiveDir)%(Filename)%(Extension)">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+  </ItemGroup>
+
+</Project>

+ 14 - 0
Peer2PeerSample/Platforms/iOS/Program.cs

@@ -0,0 +1,14 @@
+using Foundation;
+using UIKit;
+
+namespace PeerToPeer.iOS
+{
+    public class Application
+    {
+        // This is the main entry point of the application.
+        static void Main(string[] args)
+        {
+            UIApplication.Main(args, null, typeof(AppDelegate));
+        }
+    }
+}

+ 0 - 97
Peer2PeerSample/Program.cs

@@ -1,97 +0,0 @@
-#region File Description
-//-----------------------------------------------------------------------------
-// PeerToPeerGame.cs
-//
-// Microsoft XNA Community Game Platform
-// Copyright (C) Microsoft Corporation. All rights reserved.
-//-----------------------------------------------------------------------------
-#endregion
-
-#region Using Statements
-using System;
-using Microsoft.Xna.Framework;
-using Microsoft.Xna.Framework.GamerServices;
-using Microsoft.Xna.Framework.Graphics;
-using Microsoft.Xna.Framework.Input;
-using Microsoft.Xna.Framework.Net;
-
-#if MONOMAC
-using MonoMac.Foundation;
-using MonoMac.AppKit;
-using MonoMac.ObjCRuntime;
-#elif IPHONE
-using MonoTouch.Foundation;
-using MonoTouch.UIKit;
-#endif
-
-#endregion
-
-
-namespace PeerToPeer
-{
-	
-	#region Entry Point
-#if MONOMAC
-	static class Program
-	{
-		/// <summary>
-		/// The main entry point for the application.
-		/// </summary>
-		static void Main (string[] args)
-		{
-			NSApplication.Init ();
-			
-			using (var p = new NSAutoreleasePool ()) {
-				NSApplication.SharedApplication.Delegate = new AppDelegate();
-				NSApplication.Main(args);
-			}
-		}
-	}
-	
-	class AppDelegate : NSApplicationDelegate
-	{
-		
-		public override void FinishedLaunching (MonoMac.Foundation.NSObject notification)
-		{
-			PeerToPeerGame game = new PeerToPeerGame ();
-			game.Run ();
-		}
-		
-		public override bool ApplicationShouldTerminateAfterLastWindowClosed (NSApplication sender)
-		{
-			return true;
-		}
-	}
-#elif IPHONE
-	[Register ("AppDelegate")]
-	class Program : UIApplicationDelegate 
-	{
-		private PeerToPeerGame game;
-
-		public override void FinishedLaunching (UIApplication app)
-		{
-			// Fun begins..
-			game = new PeerToPeerGame();
-			game.Run();
-		}
-
-		static void Main (string [] args)
-		{
-			UIApplication.Main (args,null,"AppDelegate");
-		}
-	}
-#else
-	class Program
-	{
-		public static void Main(string[] args)
-		{
-			using ( PeerToPeer.PeerToPeerGame game = new  PeerToPeer.PeerToPeerGame())
-			{
-				game.Run();
-			}
-		}
-	}
-#endif
-	
-	#endregion
-}

+ 0 - 6
Peer2PeerSample/Properties/AndroidManifest.xml

@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="monogamesamplespeer2peerandroid.monogamesamplespeer2peerandroid">
-	<application android:label="MonoGame.Samples.Peer2Peer.Android"></application>
-	<uses-sdk />
-	<uses-permission android:name="android.permission.INTERNET" />
-</manifest>

+ 140 - 0
Peer2PeerSample/README.md

@@ -0,0 +1,140 @@
+# Peer2Peer Sample - MonoGame 3.8.4
+
+A MonoGame sample demonstrating peer-to-peer multiplayer networking using a simple tank game. This project showcases how to implement network sessions with multiple players sharing game state in real-time.
+
+## Project Overview
+
+This sample implements a multiplayer tank game where players can:
+- Join or create network sessions
+- Control tanks with keyboard/gamepad input
+- See other players' tanks in real-time
+- Experience synchronized gameplay across multiple devices
+
+The project uses the Lidgren.Network library for peer-to-peer networking and demonstrates MonoGame's networking capabilities.
+
+## New Project Structure
+
+```
+Peer2PeerSample/
+├── Core/                       # Shared game logic and classes
+│   ├── Peer2PeerSample.Core.csproj
+│   ├── PeerToPeerGame.cs
+│   └── ... (other shared files)
+├── Platforms/
+│   ├── Windows/
+│   │   ├── Peer2PeerSample.Windows.csproj
+│   │   └── Program.cs
+│   ├── Desktop/
+│   │   ├── Peer2PeerSample.DesktopGL.csproj
+│   │   └── Program.cs
+│   ├── Android/
+│   │   ├── Peer2Peer.Android.csproj
+│   │   └── MainActivity.cs
+│   └── iOS/
+│       ├── Peer2Peer.iOS.csproj
+│       ├── Program.cs
+│       └── AppDelegate.cs
+├── Content/                    # Pre-built .xnb assets
+├── Resources/                  # Android resources
+├── Properties/                 # AndroidManifest.xml, etc.
+├── README.md
+└── Peer2PeerSample.sln         # Solution file referencing all projects
+```
+
+## Supported Platforms
+- **Windows** (.NET 8.0-windows with DirectX)
+- **DesktopGL** (.NET 8.0 with OpenGL - cross-platform)
+- **Android** (.NET 8.0-android, minimum API 21)
+- **iOS** (.NET 8.0-ios, minimum iOS 11.0)
+
+## Prerequisites
+- .NET 8.0 SDK or later
+- Visual Studio 2022 or VS Code
+- For Android: Android SDK and emulator/device
+- For iOS: Xcode and iOS device/simulator (macOS only)
+
+## Building the Project
+
+### Using Visual Studio
+1. Open `Peer2PeerSample.sln`
+2. Select your target platform (Windows, DesktopGL, Android, or iOS)
+3. Build and run (F5)
+
+### Using VS Code
+1. Open the project folder in VS Code
+2. Use Ctrl+Shift+P and run "Tasks: Run Task"
+3. Choose from available tasks:
+   - `build-windows` - Build Windows version
+   - `build-desktopgl` - Build DesktopGL version
+   - `build-android` - Build Android version
+   - `run-windows` - Build and run Windows version
+   - `run-desktopgl` - Build and run DesktopGL version
+
+### Using Command Line
+
+```bash
+# Build Windows version
+dotnet build Peer2PeerSample.Windows.csproj
+
+# Build DesktopGL version  
+dotnet build Peer2PeerSample.DesktopGL.csproj
+
+# Build Android version
+dotnet build Peer2Peer.Android.csproj
+
+# Run Windows version
+dotnet run --project Peer2PeerSample.Windows.csproj
+
+# Run DesktopGL version
+dotnet run --project Peer2PeerSample.DesktopGL.csproj
+```
+
+## Content Pipeline
+
+This project uses pre-built .xnb content files located in the `Content/` folder:
+- `Font.xnb` - Sprite font for UI text
+- `Tank.xnb` - Tank texture
+- `Turret.xnb` - Tank turret texture
+- `gamepad.png` - Virtual gamepad for mobile platforms
+
+No Content.mgcb file is needed as the project uses the existing compiled content directly.
+
+## Project Structure
+
+```
+├── PeerToPeerGame.cs          # Main game class
+├── Program.cs                 # Platform-specific entry points
+├── Activity1.cs               # Android activity
+├── Tank.cs                    # Tank game object
+├── Content/                   # Game assets (.xnb files)
+├── Properties/                # Platform manifests
+│   └── AndroidManifest.xml
+├── Resources/                 # Android resources
+├── Info.plist                 # iOS app info
+└── *.csproj                   # Platform-specific projects
+```
+
+## Key Dependencies
+
+- **MonoGame.Framework.DesktopGL** 3.8.* - Cross-platform OpenGL
+- **MonoGame.Framework.WindowsDX** 3.8.* - Windows DirectX
+- **MonoGame.Framework.Android** 3.8.* - Android support
+- **MonoGame.Framework.iOS** 3.8.* - iOS support
+- **Lidgren.Network** 1.0.2 - Peer-to-peer networking
+
+## Controls
+
+- **Keyboard**: Arrow keys or WASD to move tank
+- **Gamepad**: Left stick to move, right stick to aim
+- **Mobile**: Touch virtual gamepad controls
+
+## Networking
+
+The game creates or joins network sessions automatically. Multiple instances can be run on the same machine or across different devices on the same network to test multiplayer functionality.
+
+## Troubleshooting
+
+- Ensure firewall allows the application through for networking
+- For Android, ensure Internet permission is granted
+- For iOS, ensure network permissions in Info.plist
+- Use DesktopGL version for best cross-platform compatibility